State management can make or break your React app. If your state flow is sound, your development speed will stay high, even as the complexity of your project grows. A poor state flow design, on the other hand, will exponentially raise development time.
In this post, I’ll outline some tips and pointers for effective state management in React that I picked up over the years. I won’t cover any specific state management library or solution. Instead, I’ll focus on the core concepts and principles for effective state management in React.
Spend time planning your state
Spend time planning your state management before you start coding your components.
Think about the role of the state in the feature. How can you make it easy to extend it in the future, and which data structures should you use? It might seem like a lot of work upfront, but it will save you time and headaches in the long run as the new business requirements come in.
Avoid keeping the state in different places
A common mistake I see is a component with multiple sources of state, which makes it hard to visualize the state logic and the flow of the data.
I’ve seen one component rely on URL params, Redux, and local storage. Unsurprisingly, it was hard to decipher what was going on with it.
Instead, aim to have one state source within your component. If you have a form that relies on Redux, make sure that it gets all of its data from Redux.
Of course, sometimes, you have to use multiple sources of state. But that doesn’t mean that you can’t abstract them from each other.
For example, if the same form needs to get its initial data set from URL params, wrap it in another component and have that component read URL params and pass them as props. This way, the core component stays loosely coupled, and it’s easier to reason about its inner state logic.
Derive your state whenever possible
The derived state is a technique that I think is underrated. The idea is to store the least amount of state data possible.
To avoid storing state variables, you derive them on the fly instead, which simplifies keeping your data in sync when changes occur.
Say you have multiple checkboxes, and you need to display an additional label when all of them are selected. Instead of storing a separate “isAllSelected” variable, you can compare the number of selected checkboxes against the total number on each render. If both are equal, you know that all checkboxes are selected.
It may seem like a small optimization, but it adds up when you have a lot of state variables that need to be kept in sync.
To learn more, check out this blog post about the derived state I wrote.
Extra API calls are acceptable
Some developers believe that making a “redundant” API call is one of the greatest sins you can commit in web development.
In reality, sending an API call to refresh the state can simplify your code a great deal. You can avoid writing complicated logic to keep your local state in sync with database changes.
API bandwidth is not the most precious resource of software companies, it’s developer hours. And when you write code to keep your state in sync without relying on an extra API call, you add complexity that will cost you to maintain.
There are exceptions to this, say if you’re working on a software product used by hundreds of thousands where introducing redundant API calls would visibly worsen the app’s performance and UX.
As with everything, use good judgment and consider the trade-offs. Making an extra API call might not be the most performant approach, but it’s usually the simplest and easiest to reason about.
Global vs local state
I often see newbie React developers store almost every bit of data in a global state without much reason.
This affects the performance and complexity of your code. It’s better to use the local state whenever you can. You should only use the global state for data used by multiple pages in your app.
The local state also typically has better performance. Making updates to the global storage can trigger updates to many components that tap into it, even if they don’t use the updated data.
Actions that are triggered often, such as user typing, should be kept locally.
Avoid deeply nested state objects
Deeply nested objects can be hard to work with, especially when you need to update a field several levels down.
Try to keep your state structure flat because it’s easier to work with and mutate.
One benefit of having an object with all the data your component needs is simple and quick access. Well, there is another data structure that will give you the same benefit: maps .
Maps provide san instant access to values without filtering or searching for it. Maps are also loosely coupled, meaning you can easily add or remove items without affecting other parts of the state.
Generic update handlers
In React, you’ll work with a lot of event handlers. When the user types, toggles a radio button or clicks the delete button, you’ll need to provide a handler.
Often I see developers create a unique handler for each type of event. And while this works, it’s unnecessary and leads to a lot of duplicated code. Instead, you can create a generic handler that takes the field key to be updated and the new value.
This way, you only have to write one function, which you can reuse for all your event handlers.
The code for this function might look something like this:
Then, you can use it in your child components like so:
You can even take it a step further and provide a handler that accepts the updated object. Say you have a post object with title, body, and author fields.
This way, you can reuse this
updatePost function for any event that triggers a post update, even the one that updates multiple object fields.
And that’s all for this post. I covered the most useful tips and pointers for managing React state that I’m aware of. I hope you found this post helpful and that you’ll be able to use some of these tips in your projects.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.