What you need to know before using React libraries like Redux
If you are reading this, you are probably wondering if your app or website needs a third-party library for state management — or maybe you think it doesn’t need it at all.
Here I will explain when it can be worth it and when it can be useless for your application.
State in React: Possible issues
It is a core concept because it keeps track of the status of our application and describes it over time.
In particular, React is composed of several components, each of which may have a state, and with the introduction of the hooks, we can use state also inside functional components.
Regardless of whether we use functional or class components, when working with React, we pass data from parents to the child via props.
Sometimes these data must be shared between many children within the application and sometimes very distant inside the component tree.
With the growth of our application, this could become the first problem for performance and code quality.
If we want to share the state between more children, we must keep it in a higher-level component.
However, by doing this, and especially if our structure is more complex than a to-do application, we could run into a second problem.
We all know that React components automatically re-render if there is a re-rendering of the parent component or an update of their props or state.
So every time there is a state change in the parent, all children will re-render, too, even if they are not using a state.
If you have react-dev-tools installed in your Chrome browser, you could easily check this by enabling the “Highlight updates when components render”.
Of course, a re-rendering doesn’t imply that there is a real update of the objects in the DOM.
This is because React uses a Virtual DOM, a virtual representation of DOM.
Every time the state of our application changes, the virtual DOM gets updated instead of the real DOM. Every time an object in Virtual DOM has changed, React updates only those objects in the real DOM.
Take a look at a great article by Kent C. Dodds.
ContextAPI: Is it worth it?
Sometimes we think that a solution can be using ContextAPI to avoid passing props through intermediate elements.
In short, we use a Provider to pass the values to the tree below, and any component can read it.
The consuming components called Consumer that are descendants of this Provider can subscribe to this context changes.
All consumers will re-render whenever the Provider’s value prop changes.
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
With this kind of solution, we can share the state between children, but the second problem remains because if the values inside the context change, all components will re-render anyway.
Because of this, ContextAPI can be helpful only in a few situations, for example, a change of preferred theme, language, and settings, because the evolution of their values is not very frequent.
In general, it is a good solution for the prop-drilling problem when the data is passed from a higher-level component to a lower-level part, and we want to avoid giving the props to every level of the tree.
So we can use it to solve the first problem of the share of state between children but need something else to prevent re-rendering.
A possible solution: a third-party library
A solution for this is using a third-party library for state management to improve code quality and performance.
There are a lot of exciting libraries to share the state of our components in the whole app and keep it in a store, like Recoil or Redux.
The usage of this kind of library allows us to share data among all the components and, at the same time, keep code simple and prevent re-rendering.
In this way, only the components that need that value will be re-rendered, and they can change the state too.
One of the most famous libraries that can help us to better handle complex and nested state logic is Redux, a library for managing and updating application states by using a single store to contain them.
The whole global state of your app is stored in an object tree inside a single store. The only way to change the state tree is to create an action, an object describing what happened, and dispatch it to the store. To specify how state gets updated in response to an action, you write pure reducer functions that calculate a new state based on the old state and the action.
I will not explain how Redux works because there are many guides out of here, but I only want to highlight the improvement it has had with the introduction of the Redux Toolkit.
Initially born to reduce boilerplate code of “classic” Redux and help simplify the store’s configuration, it is the standard way to write Redux logic.
After trying it with and without Toolkit, I saw how simple it has become to implement it in the code, especially with the introduction of the createSlice function.
I think that Redux Toolkit is a very established and powerful library but even after this, I think the code remains a bit boilerplate, especially for a beginner.
But there is another library that keeps it all simple and minimal.
Recoil is a very young open-source library created by a software engineer at Facebook to apparently solve exactly our problem with state management.
It provides a global state like Redux so we can share it to all components in our application but minimally and concisely if compared to other libraries, which will make you feel in love with it.
Another key feature is that it helps you keep code very simple, without extra logic, cutting the learning curve for new adopters.
It’s also an ideal choice for already-existing projects.
In short, it uses units of state called Atoms that components can subscribe to. They are updatable and subscribable, and when an atom is updated, each subscribed component is re-rendered with the new value.
Also, it provides a valuable function called Selector, used to replace the state with derived data without modifying the components that use it.
An atom represents a piece of state. Atoms can be read from and written to from any component. Components that read the value of an atom are implicitly subscribed to that atom, so any atom updates will result in a re-render of all components subscribed to that atom.
I will not write a detailed guide on Recoil, too but I only want to highlight the simplicity of the implementation and the usage.
In fact, the shared state has the same simple get/set interface as React local state.
The reading and writing on these little units are very simple and immediate like the useState hook.
Also, atoms functions are very minimal, and we can create one even with a few lines of code.
So you can start to use Recoil where you need it with very few lines of code.
How to use it in your application: my consideration
The simplicity of adoption of Recoil, or the huge popularity of Redux, could also be a trivial choice for developers that could be tempted to use and abuse an external state management library in every state of the application.
When it comes to using a such library, it’s really easy to use it to solve all the problems without any effort, but we need to be careful because the performance of the application can be reduced by the abuse of these tools.
It is better to use it where we need to share the state among many components or in very nested ones. In most cases, where we only need to pass it to very few children, saving the state into the common ancestor is better.
Time for a short recap to wrap things up. When it comes to using an external library to share state in the application we need to consider a few important things:
- If you only want to share global states for all the components like themes or settings, the ContextAPI could be a good solution because these are not values that change very often.
- As I said before if your components re-render very often, not necessarily have a bad impact on the performance.
- If some components re-render too often and you can see a reduction in performance or you struggle to share states between very nested children, maybe you should consider using these libraries to simplify your code or improve the performance. So please carefully consider if you really need to solve this issue by adding a context or an external library.
- If you need to use it, remember to use it sparingly and try to not solve all your problems in this way because there could be more efficient solutions.
So in general, depending on your application and the issue you are facing, if you need some help a third-party library can be used to solve some problems that otherwise could complicate your life.