How to manage your global state in a React app?
What options do I have to manage my React app state? What are the different state management libraries available?
Managing state in a React application can be challenging because of the complex interactions between components. And as an application grows and evolves, it is easy for the state to become difficult to manage and maintain, which can lead to bugs and other issues. Thus, it is essential to have a strategy in place to manage data. Whatever state management library is used, a robust application well-architected and thoroughly designed can make a massive difference in the developers’ productivity.
Thanks to the development of React and new libraries, there are many state management libraries and approaches for React applications, not just Redux. This includes React-Query, MobX, Zustand or even just the Context API in React. Each of these alternatives offers its own unique set of features and benefits, and the right choice for a particular application will depend on the specific requirements and needs of the project. It is important for developers to evaluate the different options and choose the approach that is best suited to their particular use case.
Where should I store my data?
It’s imperative to understand what kind of data needs to be stored and where it should live. As a developer, the question to ask ourselves is: Does my entire application need to know about this?
Once we know the type of data we have, we can decide how best to manage it. Let’s look at different types of data and states we commonly have in a React application and how you should deal with those.
Disclaimer: this list is not exhaustive. There are other types of data not mentioned and other libraries that could be considered. I have decided to list what I believe is easy to use and can work in most cases.
Server state or API data
Store globally
That’s probably the primary type of data most developers have to deal with. Fetching and updating server data happen all the time. To cache the data at the front-end application level, we want to store this at a global level. While some data might not be used by the entire app, separating the data layer is desired to simplify any application's growth and evolution.
My personal preference for storing API data is React-Query. It’s a great library that resolves asynchronous data fetching, frequent updating, offline caching, and synchronization with the APIs. You can read this noteworthy article talking about how React-Query is sometimes the only library needed, especially for API cached data.
A few other libraries can also be used, such as SWR or Apollo Client if you use GraphQL. And if you really want to keep using Redux, you can also consider RTK Query.
You can find a comparison with other global data managing libraries here, looking at different points and key features to help you decide which one is more relevant for a project.
Form data or user entry data
Store locally
Storing form data is pretty standard for many applications. A form is usually a small part of an app that collects user data. However, there is often no reason to share this data at the global level of any application. Keeping this data at the local level makes more sense, even if the form goes through multiple screens.
One library to look at is react-hook-form, which provides form state management and validation solution. It is easy to use and performs really well compared to some other form libraries. Formik is also an excellent library.
For relatively simple form or data entry, using React Context directly can also be an option. The data would then be stored in a React state and propagated via the Context consumer.
Navigation state
Store globally
Navigation-related states, such as knowing the screen currently visible, or the parameters available, are something an application might need to access from anywhere. However, managing this type of data ourselves can be complicated and probably not a good idea.
There are a few libraries available to manage navigation. For example, React Router for web apps or React Navigation for mobile apps with React-Native. Those libraries provide everything needed in terms of data and orchestrate the navigation between screens.
Components state
Store locally
There are times when it’s needed to share a component state with another component. For example, a music player that shares the “Play” state with different components to display information or create animations.
One simple option is to have a React state on a top-level component and pass down the state via props. This is called prop drilling. If the number of nested components is large, React Context is probably one of the best options. The state is set at a top-level state then passed down via the Context. Recoil can also be a good library to consider. It’s an atomic state management library that makes sharing small states between components easy. Zustand can also be a good option for this.
For more advanced components, sets of components or even multiple screens working together, you might also want to consider something like XState to create state machines.
Modal view or similar types of global components state
Store globally
When some components are visible globally, such as a global modal you can trigger and show from anywhere in your app, then it makes sense to store its state globally.
Similarly, when it is needed to share a state between 2 components that are very “far” from each other, React Context with a Provider set at the very top level of the application can be the solution. Recoil or even Zustand can be alternatives as well. And Redux, in combination with Redux Toolkit, can be an option if you really want to, as long as the data is serializable and there is no need to pass callback functions.
User preferences or user session data
Store locally or globally
When there is data associated with the current user, such as an access token, or storing the app dark mode state, in most cases those need to be stored globally. But there could be some cases where only a subset of the app needs to store and access user data.
On the web, it’s common to store those in a cookie or the local storage. So the data is not lost when the user refreshes the page. However, on a mobile app using React-Native, this type of data is better stored on some sort of state management which performs better than using the AsyncStorage or any type of local database.
Simply using React Context at the top level to store this type of data is probably enough. Redux could also be an option if you have complex states to manage.
Configs and feature flags
Store globally
I’ve seen configs, feature flags and similar static data stored in Redux because it felt like the right place. But if the config data is static and never changes, or it doesn’t require to trigger a UI change, then it doesn’t need to be in any global state. Instead, configs can live in static files and get imported when you need them. Though, when a config comes from an API, it can be stored in a cookie or a javascript object and exported as needed.
However, if your config changes during the lifecycle of your application and requires you to make additional API calls to retrieve the changes, you might want to consider using one of the libraries mentioned for the Server state above.
What about just Redux?
Redux is the leading state management library for React applications. It is a popular and widely used state management tool. It is used by millions of developers and over 50% of all React projects use it, either small or large-scale on both the web and mobile apps with React-Native.
But is Redux still the right choice for storing data?
Redux is often brought to a project because it solves the problem of passing data to any component. It was one of the only options worth using for many years. Developers used it as a silver bullet for all the data problems.
But when it’s not properly used, as developers tend to use it as a silver bullet for all the data problems, it can add complexity to an application and may make it difficult to understand and maintain.
“People often choose Redux before they need it.”
Dan Abramov, co-author of Redux.
With the introduction of new tools such as Context and hooks in the React ecosystem, more developers consider Redux as their last option. And its actual usage in projects is becoming a factor in hiring and keeping developers. Due to its verbosity and the difficulties to type with Typescript, developers are considering alternatives, despite new tools and utilities recently built around Redux.
Nevertheless, this is a controversial topic. And there are still a majority of developers that wouldn’t even consider other state management options.
Do I still need Redux?
When you stop using Redux for managing your API data, it makes your Redux store so much lighter, and it can feel overwhelming to use it for the other types of data or states. But again, that really depends on the data that needs to be managed. Redux does resolve a problem of global state management, and if that works fine in your project, then there is no reason to replace it with anything else. But if you feel it has been painful to manage data and states in your application and it is slowing down the development team, then it can be a good idea to consider alternatives.
Conclusion
Each library has its purpose and different ways of working. Some resolve state management very easily for small amounts of data, while others are more advanced and provide excellent tools to simplify your day-to-day development.
I usually go with React-Query to manage and cache API data, react-hook-form for user entry and I try to stick with Context for the rest when possible. I might also consider Recoil for sharing very small states between parts of my application.
Those tools working together have allowed me to manage all my application states without Redux, even on very large-scale applications with hundreds of screens. They tend to be easier to use and learn than Redux and they resolve data and state problems very concisely.
About me
Hi, I’m Alexis! I’m a Mobile Engineer focused on supervising and building applications with React-Native and Typescript. I specialize in solution architecture for large-scale applications, and I enjoy spending countless hours learning and playing with new technologies to use in my next projects.
You can follow me on Twitter: @alexmngn