Managing state in our applications is one of the hardest parts about React because if not carefully thought-through, it has the potential to get complicated.
There are many various techniques and tools for state management. From the local component state through more native to React solutions such as Context to the full-fledged libraries such as Redux, and of course the latest kid on the block, a kind of a hybrid, Recoil.
This post will provide an overview of various available tools to manage your React app's state as of today. It won't provide any answers, however, regarding which one you should choose for your application.
A Predictable State Container for JS Apps
For some, it is still the number one choice when developing apps with React. In my opinion, it gained popularity by centralizing the state and making it accessible by any component. And such it solved the prop drilling problem (passing data from a parent through every single child to the component that actually needs that piece of state, down in React tree). As such it reduced the number of repetitions and time spent figuring out where the props came from.
It also gave the developers the patterns and structure to follow that you can't easily diverge from. Although it may have a rather steep learning curve, once you've learnt the concepts such as: store (one object that holds all the application state), actions (events, the only way data gets to store), reducers (pure functions that take the current state and an action as arguments and return a new state value) and especially if you are familiar with the functional programming paradigm, you will find yourself at ease working with Redux.
Redux Store is a global immutable object where the state and logic of your entire application lives. This state tree cannot be changed directly. A new object is created every time a change happens.
The problem with Redux is that it it makes you write a lot of boilerplate code, and so the logic can be hard to follow. Updating the state requires the use of actions and dispatch calls, interacting with reducers, all of them in different files. Tracing the flow of data requires heavy cognitive load. I found the teams that had a very strict folder organization rules and the prescribed ways of working with Redux were much happier with it overall.
But there is another issue. Some people have a tendency to over-use it; moving all the state to the store, even the local component state. Make sure you have a valid case to moving your state out from local component according with the design principles.
All the restrictions mentioned above might make you want look elsewhere, but they are the same reasons why Redux has become so popular in the first place.
It is important to note that Redux can be used with other view libraries, like Angular or Vue.
The philosophy behind MobX is very simple:
Anything that can be derived from the application state, should be derived. Automatically.
In trying to solve the problem of decoupling the state from the individual components as well as sharing the data between them, MobX is similar to Redux. Both rely on the concept of the store for data. However, with MobX you can have multiple mutable stores; the data can be updated directly there. As it is based on the Observable Pattern: [the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes. It allows you to track the updates to your data automatically. While the components are being rendered, MobX builds the dependency graph to track which ones need to re-render when an observable variable changes.
This solution might be for you if you are familiar with the transparent functional reactive programming principles. To read more on why you might want to use MobX versus Redux, I recommend this post by Michel Weststrate, the author of MobX.
The interesting part of this solution is that it was there from the very beginning. In all fairness though, it wasn't that long time ago when it became fully supported by React API. Also the modern React additions, such as hooks made it all so much easier to put together. Plus, if you are missing Redux, you can use useReducer hook 😉
It enables sharing data between multiple components and keeping the data close to where they are needed. By looking at your tree, you pick the lowest level common parent and wrap the context provider around it.
Provider passes data directly to the consumer without having to drill through every level of the tree via props. Best part of this solution is that it only affects certain parts of the React tree, and the rest of the components may not even know that anything is happening.
One word of caution, do not reach for context too soon. Keep your data locally if the data is not needed in any other component. Also, let's not forget that passing props has always been integral part of React. There is nothing wrong in using it when your app is small and if it is just a couple of layers down. React documentation points out that a lot of problems stem from the incorrect composition.
It is one of my preferred way of managing state in the React applications. Mostly because no additional libraries are needed. Once you've learnt React - there is no added learning curve. And most of all, it is easy to share the state between the un-related leaves in the React tree without having to lift your state up.
In order to make it all stick a little bit more, let's implement Theme Switcher as an example of how you can do this.
Theme Switcher functionality will allow you to toggle between 'light' and 'dark' mode in the application:
You start with creating context for the state you want to share between your components. In our case, we want to share the theme mode. We also create Provider and Consumer to then plug into the components actually using this data:
Since most of our application will use the same theme, we will wrap the whole app in our provider:
Any component that needs to use that state will be wrapped in the consumer. Note we omitted Header from this wrapper:
💥 What if I want one of my components to be of a different theme than the rest of the app?
Pass the theme via props instead! The Header example below also implements its own button to change the state just to illustrate the point, but of course in real life it is an overkill 😁
In my app's case, I want my Header to always be in the light mode, so it uses the default theme:
You can notice that clicking on Change Theme button, changes the theme for the whole app, but not Header.
React Context allows you to have a Provider at the top of the tree and consumers that are listening to it. This is great as it allows us to pass the state between the parts of the React tree that are not connected in any way, without much extra code.
However, that might be not enough for more complicated problems. Say you want to generate a list of items on the fly and you don't know how many items you will end up having, how do you insert the Providers then?
Luckily, that's been solved now with using the latest addition to the React world i.e. Recoil.
Recoil works and thinks like React. Add some to your app and get fast and flexible shared state.
It is said to be very minimal and adding "extra little ingredients to React". It focuses on solving flexibility and performance limits.
As an example, let's think of an application where you needed to share your users' name between Header and Body. You will keep one useState hook in your Header component and the second one in your Body component. With Recoil, however, we can share this state without having to keep it in sync in two different places.
That is enabled by the concept of Atoms, a piece of state that other components can subscribe to. Any change with an atom will cause a re-render in all components subscribed to it:
This graph has been stolen directly from Dave's conference talk. I recommend you watch it in order to understand the building blocks as well the use-case for Recoil.
With Recoil, the leaves in the React tree can co-operate with each other without having to go through their common ancestor or having to create any reducers. Recoil allows for the application wide state observation.
In his presentation, Dave explains in great detail the advantages of this solution. Additionally, if you are familiar with hooks, the learning curve will be minimal for you. if you are building an application where performance is a key, you may want to check out this library. I can see a lot of us reaching for this solution in future, especially for more complex data management problems.
If you would like to give it a try, head over to getting-started guide of the Recoil documentation where you can learn how to implement a to-do application using the new concepts.
Please note I have not used Recoil in production yet, so these conclusions are based on the research, not application.
All in all, it may seem overwhelming and hard to grasp why there is so many choices for the state management. And if you are new to React, and have not been following the journey of how it's been changing over the years, I understand your frustration. However, I strongly believe that all the recent additions and changes have made the lives of developers much easier after the initial phase of dismay over yet another React library or a concept to learn.
Good luck with making your choice!
Please note: this post has originally appeared on my blog
Main Photo by Michael Dziedzic, thanks for sharing your work on Unsplash.