Have a problem before you solve it
In popular discourse on Twitter about React, there is a fascination in the community with making sure components do not re-render unnecessarily. A lot of best practices and strategies are recommended such as restructuring the application or using making use memoization strategies like useMemo
, memo
and useCallback
.
When it comes to handling state, it is generally advised to keep state local whenever possible. If you need shared or global state that will be used across multiple parts of the application, it is preferable to use external state management libraries such as Zustand or Redux instead of relying on React context.
Keeping global state in React context should be avoided, unless it is state that does not change often. This is because when state in context changes, the entire part of the app that consumes that state will re-render.
These optimisations & best practices are what developers are advised to do by default when writing React applications and as someone who works with React quit a lot i took these best practices to heart.
But, did I fully understand why these are considered best practices or why a lot of influential people in the React community advise following these strategies?
At Super, we have a fairly large front-end application written in React. This application is very dynamic and client side heavy, we rely a lot on complex state:
On my first day, I reviewed the codebase and discovered that React context was used extensively throughout the application for sharing and managing state.
When I came across this, I thought we could restructure the application to reduce reliance on React context for sharing state. Instead of using React context, we could use Zustand to manage state outside the React tree. This change should improve the performance of the application.
What is the point of removing state from context? — To minimise unnecessary renders.
Why do we want to reduce unnecessary renders? — To improve the performance of our application.
But, the application worked fine with React context. At that point, I had encountered no performance issues. Our UI was performant, and there was no jank or performance issues caused by excessive re-rendering.
So what would be the point of introducing an external state management library preemptively to the codebase, to solve a problem that we don't have yet? This would only add unnecessary complexity to the application for seemingly no good reason.
The problem with best practices is that, while they are made with good intentions and are helpful most of the time, there is beauty in encountering a problem, understanding it in the specific context in which it arises, and solving it instead of preemptively doing so.
If you've read about useMemo
, memo
, or useCallback
, you may understand why they're useful and the problems they solve. However, if you haven't encountered the specific problem they solve in the context of the codebase you're working on, you may not fully appreciate their benefits. Don't miss out on this valuable learning opportunity by preemptively solving a problem that doesn't exist.
Running in problems and solving them is a good thing.
For example, at Super I had to create an Icon picker for our version 3 release:
As you can imagine, this component is quite heavy, with a long list of rendered components. After building out the first iteration, I ran into performance issues. The scroll was janky, searching for icons was slow, and the component took a while to show up on the screen when triggered.
I searched for a solution to this problem and discovered the list virtualization technique. This technique is used to efficiently display large lists of data by rendering only the visible list items in a scrolling viewport.
Using the technique with the icon picker significantly improved its performance. It felt like magic and it gave me a deeper appreciation and understanding of list virtualisation — I encountered a problem in a specific context, searched for a solution, found one, and applied it.
There is no better learning experience than encountering problems and fixing them.
Best practices have good intentions, but you never fully understand their usefulness until you appreciate the problems they try to prevent in your specific context. Embrace encountering problems and fixing them.
Have a problem before you solve it.