‘State is Hard’ — Where to Handle State in React?
‘State is Hard’ — Where to Handle State in React?
When we write software, one of the biggest issues is how should we handle the state? The user has a form to fill in and updates the fields. Should the data, thus the state, be saved in an object or be managed by a library so that we have easy access to it in other places as well? Or should the state just be reflected in the DOM with the value of the input fields?
When the data is sent to the server, how do we handle the persistence? Should we use a relational database or some new and fancy NoSQL database? And more importantly: How do we handle the mutation of it from multiple users?
With the help of some frameworks, many of these questions are answered for us and we can move on. Backend frameworks like Rails, Django or Laravel are handling a lot of the database problems for us. How can we solve the state management on the front-end?
When we are using React many questions stay unanswered until we take time to solve them since React is very unopinionated. When we do so, we should think about the different kinds of states: the local state, and the global state.
What do I mean by local state and global state?
The distinction is quite simple: Global state is data that might be rendered on different areas in the UI. Sometimes this data might be updated via different forms. And more often than not some UI components show some icons/badges or other stuff according to a certain state. The “new message received” icon in Facebook or LinkedIn or other web apps is a good example for this. As a rule of thumb, everything that we are fetching from the server should be considered to be a global state.
On the other hand, a local state is data that represents how the UI is displayed or data that is volatile i.e. not persisted. E.g. for a nested modal dialog component, it is the flag if the dialog is open. Or, it can be a logic that decides if some form fields are visible. Normally this state somehow depends on the global state and might be just derived from it.
Should we make a distinction between these two types of state?
In some architectures, famously in The Elm Architecture (TEA), every state is global. This works fine if the developer makes sure that the data structure that represents the state is efficient. For me personally I find it a bit confusing in JavaScript, but Elm makes it easy to setup a type that represents all you need without getting lost in the details.
Why shouldn’t we handle the state within our components?
Sooner or later you will have to respond to some state change in a different component and implementing a notification system will cause you a big headache.
If a data is so important that we persist it, we likely want to use it in many components. When we do this we create a tight coupling between these components and to decouple them we create a global data store which everyone has access too.¹ So, we, kind of, create an interface which these components depend on, instead of the actual implementation. Ember.js handles state management this way and it works great.
We have two options where to put the data to make it available. We can use a React Context or a state management library like Recoil or Redux.
When should we use a Context for a state?
A Context has the big advantage that it is included in React out of the box. The documentation is great, the concept is quite easy to grasp and the API is easy enough to work with. But, the use case is limited! A context should be used when it holds data that doesn’t change or at least doesn’t change often. Why is that so? Because when the data changes inside a context all the children will be re-rendered no matter if the component depends on the data. So, if we are using a Context for data that changes a lot we create a performance bottleneck.
A good use case for a context is state that many child components depend on and we therefore would have to use prop drilling. When this state changes the child components would have to re-render anyways since they actually depend on the data.
For all other data we should consider using a state management library.
Options for state management in React
Sate management libraries have a long history in React. Redux for example was released in 2015, only two years after the release of React itself. Redux was inspired by TEA.
More recently, Recoil managed to create some traction as well. Recoil integrates really well with React since it is developed from Facebook as well and they are utilizing modern React features like Hooks.
With this libraries it is of course possible to use local state as well. We can just use the useState
hook in our components.
Conclusion
State management has always been hard. But, with the help of libraries like Recoil and a little bit of forward thinking on our side it is becoming easier. We have now all the options to create well architected web apps while relying on proven tools that we don’t have to invent on our own.
¹ The store that handles the data has to be imported to actually use it of course. This is working like an access to a context or another component.