Developers turned engineering managers discuss the skills you need to level up your career.
Choosing a global state management strategy for your React app
Your global state management strategy has a significant impact on your React app and developer teams. Here's how you can make an informed choice.
William Hill is a senior software engineer at Meroxa with experience as a full-stack engineer and a DevOps engineer. His background includes creating apps for subject matter experts in nonproliferation, climate science, and basketball analytics. William is a proponent of increasing diversity in the computing field by recruiting and mentoring engineers from underrepresented groups.
Let's say you want to check the score of a game on ESPN.com. You click on the box score expecting to see the results for that particular game. However, the data that is being displayed is from a different game entirely.
The mismatch between the queried data and the data displayed on the page could be due to improper state management, which prevents your React app from syncing state across multiple components. The more your app grows, the more challenging it becomes to manage global state effectively.
There are several strategies for managing global state within React. However, not all choices are created equal for every use case. Selecting a global state management strategy is an important yet challenging part of building your app. Whether you are working on a greenfield project or an app refactor, you'll need to weigh tradeoffs regarding complexity, testing, and tooling.
Today we'll focus on the pros and cons of three popular global state management strategies:
- React Context
In the words of Black Sheep: you can get with this, or you can get with that. The choice is yours. This article will equip you with the knowledge to make that choice.
React Context is the batteries included approach to global state management. It doesn't require the use of any external dependencies since it is part of the native React API.
Sharing state amongst components and the application is simple:
- Define the Context object.
- Wrap the component tree with the Context Provider.
The result: everything nested under that context can access the data within it.
While React Context can be a good choice for fairly simple apps, it's less effective for managing complex pieces of data.
Context can only store a single value, as opposed to an indefinite set of values (each of which has its own consumers). Passing an object to a Context provides some flexibility for the single value constraint. However, that may not solve your cases as your state management grows in complexity.
It’s possible to define multiple Contexts that wrap a component tree, but defining and consuming multiple Contexts can quickly become messy. In addition, you could run into performance issues as your app grows because Context can cause frequent and unnecessary re-renders as all components in the tree get re-rendered on state changes.
When used properly, Context is a powerful state management solution, despite its limits when dealing with a high frequency of state changes.
React Context Pros
- Built-in strategy that doesn't require external dependencies
- Effective for relatively small apps
React Context Cons
- Limited ability to manage complex pieces of data
Redux stores the entire global state of an app in an object tree inside a single store. Actions that happen within the app can get dispatched to the store. From there, a reducer function derives the new application state using the previous state and the action that was dispatched, as shown in the following figure:
I think of Redux as the kitchen sink approach. As your React app grows to require more complex state manipulations and management, Redux can handle all of that far more effectively than React Context. At the same time, Redux adds a layer of complexity to your codebase and code maintenance. This can mean a steep learning curve for developers.
Fortunately, Redux has a large community and ample resources to support developer learning. The Redux Toolkit can help you navigate many of the challenges you'll encounter by providing an opinionated set of best practices.
The question is whether that learning curve is worthwhile for your use case. Do you need the capacity to manage complex pieces of data? For relatively small and simple apps, the Redux library can be overkill. For large enterprise apps, you may want to invest the extra time in learning and implementing Redux.
- Effective at managing complex state in React
- Has useful dev tools for development and debugging
- Has option for server-side rendering
- Can have a significant learning curve
- Initial setup can be challenging (although Redux Toolkit helps make it easier)
- Can have more boilerplate code than other approaches
Recoil is an experimental state management library developed by Meta (the same people who developed React). As a result, the Recoil API and coding style mesh really well with core React.
Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components. Selectors transform this state either synchronously or asynchronously. The state definition is incremental and distributed, making code-splitting possible.
As an ongoing Meta experiment, Recoil is on the cutting edge of global state management solutions. However, its experimental nature can also be a downside. Developers are constantly incorporating feedback and adjusting the Recoil methods and APIs. This means that users can experience breaking changes. On top of that, there are limited educational resources to support Recoil users.
- Has a similar API to React local state
- Helpful if you have a large number of interdependent components
- Unstable API due to being experimental
- Does not support middleware
Choosing a strategy: In practice
A previous company I worked at used Redux as the state management strategy. I experienced that aforementioned learning curve firsthand. While I got up to speed on Redux, we were also engaged in high-level conversations about global state management. Should we take a different approach?
We started by assessing why we used Redux in the first place. Part of the reason is we were on the older spec of React. As a result, we didn't have access to hooks, which allow you to use state and other React features without writing classes.
Things changed once we updated our React version and gained access to hooks. This opened up options beyond Redux. Our team discussed the tradeoffs of the strategies we've covered today.
This could work for a few isolated parts of our application. But overall, our enterprise-level app is too complex. It will be more trouble than it's worth to wrap our component tree in multiple Contexts.
This option is appealing because it's on the bleeding edge and well supported by Meta. However, the fact remains that Recoil is very new, so there are frequent changes to the library that can crash our app. Is the innovation behind Recoil worth the inevitable breaking changes?
Orienting to how we use Redux is a heavy lift. However, this strategy is reliable and enables us to manage multiple states effectively. Now that we have the option of a more hooks-based approach, we may explore moving away from Redux to simplify our code base. But for now, it makes sense to focus on how we can use Redux more intelligently.
A key takeaway from this process is that choosing the right global state management strategy starts with understanding your use case. Ask yourself:
- What is the complexity of your application?
- How might this change in the future?
There is no one-size-fits-all solution to managing global state. In addition, the strategy that works today might not be the best choice six months from now.
You can get with this, or you can get with that. The choice is yours. The tradeoffs we've discussed here, grounded in deep knowledge of your use case, will help you to make an informed one.