I have helped build massive react apps with hundreds of components, a lot of states to manage, complicated data relationships, endless routes, and complex API services. I have made and experienced the consequences of a lot of bad decisions which I would like to share with you.
1 — Lack of component building strategy
I actually wrote about a strategy to build components in another article which you can check later but, a common mistake is to jump right into creating components. Even when using an external library.
Preferably build all your components separately from the app you will use them. This will make sure you build them to work independently from the place it will be used and avoid adding app logic into them. You can do that by creating a separate library with a storybook or any other tool you like.
2 — No clear separation between view and data
One big mistake is to include data logic or any application-specific thing into your components. This will make them hard to reuse in another application or in another part of the same application. Remember, react is the view or view-model layer of your application so, make sure to handle your data logics in services or utilities separately.
3 — Usage of props in state
A state is local data for the component and a prop is data passed to your component. If you find yourself putting props data into your component state, remember that the parent component may already be managing that data and this is a red flag for bad architecture or lack of understanding of how state and props work. This practice makes any component useless and prone to bugs.
4 — Long/big Components
One thing I enjoy in Flutter and Swift UI is the concept of widgets. A widget is independent and can do elaborated things but if it starts to grow big and complex, it is probably a sign you are creating an application inside another. Big components are hard to maintain, reuse, and test. Try to abstract logic in the smaller components and use containers to compose smaller components with few props needed for rendering.
5 — No distinction between local and global state
A common mistake you find in many React apps out there is the usage of Redux or Flux to manage all states of the application. Including UI states that should be local to an area or component like flags to control whether an area shows or not. Over the years, I've been using things like Redux less and less and focus on better architecture, usage of HOC, and context for local states. Redux and flux are great for application data and not the local or UI states.
6 — Complicated data format
One thing that bites hard is the complicated data format. I like to improve my app performance by using things like pure components, memo, and useMemo but React does a shallow comparison. When you have complex and nested data, the shallow comparison does not help much. Complex data is also hard to manage and change with setState.
Try to simplify data as much as possible and move any data handling to outside of components all together supplying only a simpler piece into your components. Avoid deep nesting, weird property naming, and handling long lists in one single component. I have been using the utility component flatlist-react more but it is also about the overall app architecture and planning your data before you lay down any code.
7 — Lack of a design system
Devs either lack a design system for their component library or adopt a component library which they spend a lot of time overwriting to customize for their liking. Both options are a waste of time. Before you build anything related to UI you must agree on a language for your App. Things like a color palette, sizes, spacing, fonts, margin, all play a big role in your app-building. It does not end there though.
You must agree on a strategy or tool to help you build your component quickly. Something you can explain to someone new and off they go building components.
8 — Busy render function
Your render functions should ideally contain your state and props destructuring and the return of the JSX. Your components should receive exactly what they need and in the format, they need to do what they have to do. You can use utility components to handle any complications and even make usages of memoized functions to handle more complex logic.
Too much logic and formatting on render simply mean extra things to do on every render which can slow things down a little. Hopefully, you are not also calling setState, directly or indirectly from the render function which is simply a really bad thing to do.
9 — Constant re-rendering
This is when memoizing, simple data format, smaller components, and good application architecting really shines. No parts of your component should re-render unnecessarily. Separating global vs local state also helps a lot when it comes to re-rendering.
Simple data formats are easier to memoize and shallow compare to avoid re-rendering. Smaller components mean only a small and simpler portion of the app gets to re-render. A good app architecture means the data flow will affect only where it needs to affect. Separating local vs global states means local things update on local changes and not affect the whole app.
10 — No Unit Tests!
It is easy to overlook a component and believe it is all good. My components are better because I unit test them and you will be surprised at how many issues you find when you unit test your component. One thing I often noticed in developers that do not unit tests their React components is that their components are complex and hard to test and I would hate to test those as well.
When you split your components into smaller pieces and add tests to those, you are automatically testing the big ones which probably will need a simpler test and an e2e to go.
I learned by doing and experimenting and I am now sharing my knowledge through writing, and YouTube Channel videos, but things like these take time. You are never an expert if they continue to change and be improved upon but exposure and experimentation by curiosity is the best way to go. Keep on doing better and expect better. Anything can be improved.