FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Upgrade
Welcome to FullStack Labs. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide many other benefits of the site. For more information, please see our Cookies Policy and Privacy Policy.

Choosing the Right State Management Tool for Your React Apps

Written by 
Omar Mejia
,
Principal Software Engineer
Choosing the Right State Management Tool for Your React Apps
blog post background
Recent Posts
Getting Started with Single Sign-On
Accessibility in Focus: The Intersection of Screen Readers, Keyboards, and QA Testing
Integrating GitHub Actions and Docker for a Java Project

If you want to develop dynamic apps that respond well to user input, then it is incredibly important to be well-versed in State Management. Let’s explore some of the methods we can use to manage state in React.

Table of contents

One of the biggest challenges for modern web applications is balancing growth while maintaining logic between interface actions and data. In other words, React developers have to deal with the fact that, as applications become more complex, managing state will become increasingly challenging. That’s why it is so important to understand State Management in React and how we can use it to develop scalable and high-performance applications. 

Understanding State Management

In React development, State Management is the process of tracking how your application data changes over time, maintaining data flows, and defining the state of data at any moment. The built-in state object in React components encapsulates the data where developers store assets that are persistent across component renderings.
With all of this in mind, we can start thinking of how React applications can model behaviors based on the state of the data. But that still leaves a few unanswered questions: How do you implement state in a React app? How do you read it? How do you change it?

The built-in solution in React

One of the most notable methods to manage state in React applications is the one encouraged by the official React documentation: Hooks. Hooks are great on their own because they help developers write shorter, more readable code that is easy to share and maintain.

However, while it is true that hooks like useState and useReducer give a lot of usage and flexibility for Components, in most cases using them doesn’t prevent other problems like Prop Drilling (even using composition) or errors in shared data between different branches in the components tree. 

To tackle this, the React team developed a new built-in tool called React-Context API and the useContext hook to complement it. Using both of these we should be able to wrap components with shared states together under the same “Context” while keeping access to its data. Simple, right? Now, we only have to deal with one more problem — we need to implement it all on our own.

  • Reader: “Oh no, this looks like a lot of work for my app!”
  • Me: Yes, it is! But only for apps that share a lot of data across components.
  • Reader: … 
  • Also me: Don’t worry, in most cases we can add State Management libraries to the equation. There are plenty to choose from.
  • Reader: Nice! But wait, which one should I use? Which one is better?
  • Me: Mmmm, that is an interesting question. Keep reading!

React State Management libraries

There are several types of React libraries for State Management, but today we will focus on three: Contextual, Atomic and Hook-Based libraries. As expected, different state management libraries use different approaches to achieve the same or similar solutions. They also differ from each other in terms of library size, support for languages, documentation, API support, and more.

Contextual

We can find two of the most popular state management libraries in this category: the React-Context API and Redux (RTK). The main goal of contextual libraries is to share the state through the entire app using Flux, an architectural pattern.

Let’s imagine an app that is defined like this:

Flowchart depicting how state is managed across components in Contextual React libraries

In this graph, the Component A will: 

  • Pass the Info A data to Component B
  • Pass the Info B data to Component C

However, a problem arises when, for example, the app needs to pass Info C from Component B to Component C. How could we solve this?

Well, the answer is a Context + Provider. Let’s take a look at how this works.

Flowchart using Context and Provider to pass data across components in Contextual React libraries

Here, we use a Provider (Wrapper) and a Context as base. The context itself has different definitions and implementation of functions to add, update, delete, and reset the state or parts of it. With this strategy, we can get direct access to the same data in different components and use the functions (or actions) to add, update, delete, or reset the state whenever the app requires. There is a small price to pay, however: we can only access the data if the components are wrapped by the provider.

Atomic

Atomic libraries like Recoil and Jotai follow similar principles to contextual state managers. One of the characteristics that differentiates them is how they solve that little caveat in data accessibility we mentioned earlier. In this case, each data value is represented by an “atom”, a simple variable (like useState) that can be easily manipulated using hooks and helps us access the state outside of it. 

Flowchart depicting how state is managed across components in Atomic React libraries

While all of this might sound great, React developers should be careful of where and when atoms are being manipulated. It is not rare to cause issues in the data flow, and therefore, errors in the app.

Hook-Based

Finally, let’s go over Hook-Based libraries. To put it simply, Hook-Based libraries combine the previous two types, where instead of context or atoms, it creates stores that basically work like context, but that don’t require wrapping the components or parts of the app with a Provider for access or data manipulation. Just creating the store with the expected data structure and defining how it will be manipulated creates a state that’s easy to use with hooks or directly in parts of the app outside components. Here, we’ll find libraries like Zustand.

Choosing the Right State Management Library

After taking this trip around many of the different options we have for state management,  it’s time to take start evaluating which ones tick the most boxes for each use case.

  • React-Context API is a built-in manager for React, meaning it is a great choice if you are not looking to add more libraries to your project and size up your bundle when building your application. But, yes, you will find yourself writing more code if the complexity of the data flow increases, creating more opportunities for bugs to show up in the code base. That said, this API has been known to come up with performance issues related to the re-rendering of the component when accessing the context. Luckily, there are some ways to reduce these problems (using hooks like useMemo or useCallback), but none of these solutions are ideal yet.
  • Contextual state managers are the most popular choices currently. Nonetheless, they are still subject to re-rendering whatever was wrapped by the provider when the context changes. You can minimize this by splitting the context into more and/or smaller pieces, or try avoiding it completely by adding an additional library like reselect to pick specific data and reduce those re-renders. This will lead to more boilerplate in the code, but that might be a small price to pay to enhance your app. There is also the caveat of the learning curve: React developers will need to deal with a lot of concepts at the start to set up a state and make proper use of best practices.
  • Atomic libraries reduce the boilerplate of the context + provider and treat them like simple variables as useState does. The way that the state is created looks clean and easy to maintain, and even if each atom only manages small and simple data structures, this method keeps things simpler than a big global state or a context itself. However, as the app grows, the number of atoms will be higher and higher. This is where we can start to see the downside versus the other types — the developers will need to keep an eye on how the atom structure works (graph-like) and in some cases will have to use additional concepts like Selectors or Family to deal with situations like get, set, filter, replace, etc… a set of data. This puts the learning curve of Atomic libraries somewhere between Contextual and Hook-Based state managers.
  • Finally, let’s talk about Hook-Based. They solve some of the main issues related to the previous types, are less structured (therefore, a faster learning curve), but at the same time allow the developers to break the used pattern for the state management. The big caveat here is finding enough people who know how to use it. For most React development companies, it is not easy to find people who use this kind of technology, as the development community behind it is significantly smaller than the communities for other libraries. Still, in recent years the usage of Hook-based libraries has been increasing.

Conclusion

All solutions are valid, what we need to do is find which one accommodates the definition of the application, what is more, comfortable for the team, how important is scalability, and what kind of state management works better with your app. Keep in mind that two or more state management libraries can coexist at the same time, as long as your team keeps a very good definition of what each one is used for in specific situations. In other words, whatever tool you choose, taking into account your goal and the scope of the application will be the key to identifying the best state management tool for you.

Omar Mejia
Written by
Omar Mejia
Omar Mejia

As a Principal Software Engineer at FullStack Labs, Omar Mejia excels at overviewing the day-to-day development process and ensuring that his team's work aligns with business goals. He has over seven years of experience in the field and has become proficient in multiple technologies and programming languages, including React, TypeScript, NodeJS, Azure, Redux, Saga, MUI, and several others. Omar holds a Bachelor's degree in Systems Engineering and a Master's degree in Digital Transformation.

People having a meeting on a glass room.
Join Our Team
We are looking for developers committed to writing the best code and deploying flawless apps in a small team setting.
view careers
Desktop screens shown as slices from a top angle.
Case Studies
It's not only about results, it's also about how we helped our clients get there and achieve their goals.
view case studies
Phone with an app screen on it.
Our Playbook
Our step-by-step process for designing, developing, and maintaining exceptional custom software solutions.
VIEW OUR playbook
FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $75,000.