React State Management- All You Need to Know
Tuesday, March 11, 2025The biggest challenge for front-end developers is managing states in a React application to create a dynamic and intuitive user interface. The absence of states would make the app static and unresponsive to user input. Thus, React state management is a very important aspect.
As the complexities increase with the growing app, using React alone may not be enough. Therefore, React development companies leverage hooks and libraries to get the job done.
This article will explain the concept of React states and discuss various ways to implement it in your application. It also sheds some light on the pros and cons as well as lists some popular tools and libraries for state management in React.
1. What is React State Management?
In a React application, handling the changes in the states of components is called React state management. These components consist of a JavaScript data structure known as a state object. This state object holds assets that are persistent between component rendering.
When users interact with the app and change the state, your UI will look completely different, as it will be represented by the new state instead of the old state. State management is the basic requirement for any dynamic application. React provides simple and flexible APIs that support state management within the React components.
2. Approaches to React State Management
You can attain React state management in your application in the following ways:
2.1 Component State
This is the most common approach to state management in React. Every component of your React application will have an internal state. Any changes to this state will re-render the component automatically. Using the setState method, you can handle the local state of those components.
2.2 Context APIs
Context APIs allow data to pass through the component tree without manually passing the props at each level. This approach is a useful alternative to the component state method when you have to share the state between components that aren’t related directly.
2.3 State Management Libraries
The state is handled using state management libraries like MobX and Redux in large applications. It offers a centralized way to manage application states. This approach allows you to set the rules for updating a state and stores the state of an entire app in a single location.
2.4 React Hooks
You can enhance components using React hooks. The useContext hook allows access to shared state from the Context API whereas local component states are handled with useReducer and useState hooks.
You can also use custom React hooks to extract state and logic into reusable functions that various components use. This approach is useful for sharing state and logic between components if they are not deeply nested in a component tree.
2.5 GraphQL
In this approach, to manage the state of your application, use a GraphQL server as a middleware. GraphQL is very effective at handling the state of various types of user interactions, as well as the state of the data coming from the server.
2.6 Higher-Order Components (HOCs)
The process of sharing states between components by wrapping them with another component that offers state is called Higher-Order Components. If the components aren’t nested deep in a component tree, then you can effectively share the state without complex nesting.
2.7 Render Props
In the Render props approach, a function is passed as a prop to render a React component that needs to share the state. This is one of the best options for sharing the state between components that are not deeply nested in the component tree. You can refer to the below-mentioned Tweet.
Every state management approach in React has its pros and cons. Adopt a method that suits your requirements.
3. How to Set Up State Management in React?
This is a step-by-step guide to implementing React state management in your apps.
Step 1: Create a New React Project.
npx create-react-app counter-app
The above code helps you create a new project. After that, go to the SRC directory and create a new file named ‘counter.js’.
Step 2: Define the Main Function.
The next thing we have to do is import React to build a new functional component named TotalSum. Using the useState hook, we will then generate a new state variable named totalSum and set its initial value at 0.
Now, we move on to developing add and remove functions that would help update the totalSum variable by 5.
import React, { useState } from 'react';
function TotalSum() {
const [totalSum, setTotalSum] = useState(0);
const addAmount = () => {
setTotalSum(total + 5);
}
const removeAmount = () => {
setTotalSum(total - 5);
}
return (
{totalSum}
);
}
export default TotalSum;
The users can call both addAmount and removeAmount functions by clicking the ‘Add 5’ and ‘Remove 5’ buttons, respectively. Inside these functions, we call another function named setTotalSum to update the totalSum value. To do that, you either add or subtract 5 from the current sum.
The current value of the sum is displayed in a div element, which we shall be returning along with the buttons for incrementing and decrementing the sum.
Step 3: Render the ‘TotalSum’ in the Main JS File.
For rendering of the TotalSum component, we have to navigate to the App.js file.
import React from 'react';
import TotalSum from './TotalSum';
function App() {
return (
);
}
export default App;
Running the npm start would run the application. The sum app would be visible in your browser. This example demonstrates how to handle states in React apps.
4. Challenges in React State Management
The state management process can be challenging. If you are working on large and complex applications, you ought to face a few obstacles along the way. Some of them are as mentioned below:
4.1 Prop Drilling
Prop drilling passes the data from parent components to deeply nested child components through props. This kind of data sharing between components gets more difficult with the growth of the application. This issue can be resolved by using some external state management libraries or state management technologies such as useRef and useContext.
4.2 Complex State
The states are often made very complex. There is no problem if they are required to be complex, but if not, then handling them would be a problem. Having simple states makes maintenance and debugging easier.
4.3 Optimizing and Maintaining Performance as Application Grows
Maintaining the consistency of the states across various components would be tough for a growing or large application. Optimizing the state performance would be overwhelming. You can resolve these issues by using performance optimization tools like React Devtools, avoiding unnecessary re-renders, and optimizing state updates.
Further Reading on: React Performance Optimization Techniques
4.4 Overusing Context or Redux
The global state of the React components can be managed using powerful state management techniques like Redux and Context API. However, it’s important to use them wisely. Overusing these methods on small and simple React apps can lead to unnecessary complexity.
4.5 Not Handling State Updates Properly
It is necessary to handle the state updates correctly, otherwise the UI may not accurately reflect the current state. In such cases, you should use setState in class components and useState in functional components. useEffect can also be used to know the component re-rendering by passing the state as a dependency.
4.6 Asynchronous State Updates
Some complications may arise because of asynchronous state updates. But these risks can be easily mitigated using a state management library such as Redux or middleware like Sagas.
4.7 Mutating State Directly
Mutating the state is a common problem in React state management. Instead, it’s better to change the state by using the update function provided by useState hook. This approach makes sure that your component re-renders properly and maintains desirable behavior.
5. Benefits and Limitations of RSM
Like every tool, and every technique out there, React state management also has some benefits and limitations.
5.1 Benefits of RSM
- Better Performance: Libraries like Recoil provide effective ways of managing state in a React application. This also helps you avoid unnecessary re-rendering of the components, leading to an enhanced performance.
- Improved Scalability: Handling the state of the large apps becomes easier with state management libraries. They allow data sharing between multiple components and efficiently manage states.
- Offers Better Control: With the use of state management libraries like MobX, and Redux, you get a centralized place for storing, tracking, and updating the state of the React application.
5.2 Limitations of RSM
- Boilerplate Code: Setting up and managing the state with libraries needs a lot of boilerplate code. This makes it hard for new developers to understand the code.
- Overhead: When you implement the React state management, you are adding a layer of abstraction. This makes your code too complicated and harder to debug.
- Added Complexities: Using a state management library for RSM adds more complexities to the app. Developers must learn the library’s principles and concepts to use it effectively.
6. Tools Used for React State Management (RSM)
Some of the tools used for React state management are discussed below:
6.1 The useState Hook
The useState Hook is one of the most common APIs in React for state management. Let’s consider an example to understand the workings of this hook. But first, we have to create the state using the following command:
const [totalSum, setTotalSum] = useState(0);
This code will create a function called setTotalSum and a state variable called totalCount. These are useful in updating the state of an application. Let us set the state’s initial value at 0. To update the state, we shall increase the value of the sum by 5.
setTotalSum(totalSum + 5);
In the end, we will use the state:
Total sum is {totalSum}
This example shows how to use the useState hook to update the value of the sum in the user interface.
6.2 The useReducer Hook
The useReducer hook is used for state management along with an initial state and a reducer function. It handles the state objects with a complex state structure and multiple properties.
The reducer function invokes actions using a dispatch function and the current state, returning the updated state. Although the pattern of this function may seem similar to that of the reducers in Redux, it works locally in a component. Let’s take a look at an example of the reducer function:
import React, { useReducer } from 'react';
const initialState = { totalSum: 0 };
function reducer(state, action) {
switch (action.type) {
case 'add':
return { totalSum: state.totalSum + 5 };
default:
throw new Error('this is not allowed');
}
}
const TotalSum= () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
This point in time total sum is: {state.totalSum}
);
};
useReducer
In this example, we had a useReducer hook along with an initial state and a reducer function for state management.
6.3 The useContext Hook
The concept of the useContext hook is to save some information that can be useful to multiple components. Here, the use cases and the pattern are different. The useState and the useReducer manage the state that is limited to a single component. However, the useContext manages the state that is shared across various components.
The useContext aims to avoid prop drilling when the state is shared within a child component that is nested deeply in a component tree. Managing the themes of the React app is one of its primary use cases. Its code will look like this:
First, we have to create the context in a component near the top of the component tree.
export const userContext = createContext({});
The context is then exported to a component down in the tree.
import { useContext } from 'react';
import { ThemeContext } from './App.js';
export default function Button({ children }) {
const user = useContext(ThemeContext);
// ...
}
But make your code work, you have to wrap the component tree above this consumer component with the context Provider:
{children}
With that in place, the Provider gives the value to the consumer component. When this value is updated, the child component down in the component tree can access this updated value.
7. React State Management Libraries
Here are a few state management libraries used in React development.
7.1 Recoil
Recoil is one of the latest libraries from the React team. This approach of sharing the state is unique and so effective that Facebook is using it in many of its applications.
Instead of storing the application’s state in a single large object, Recoil uses atomic state management, which breaks down the state into small independent units called atoms.
Recoil is built upon two main elements: atoms and selectors.
The components can retrieve and update their values by subscribing to an atom. Meanwhile, the selector is the value aggregated from an atom. The selectors are re-evaluated whenever there are any changes to either atoms or selectors.
API Example:
index.js
Wrap the component that needs to use the recoil state with RecoilRoot wrapper.
globalState.js
export const totalSumState = atom({
key: 'totalSum', // unique ID (with respect to other atoms/selectors)
default: 0, // default value (aka initial value)
});
Create a state that needs to be managed between multiple files. Here we have taken ‘totalSumState’ which manages the total of our add/remove operations. Having a key ‘totalSum’ and a default value is 0.
App.js
const [totalSum, setTotalSum] = useRecoilState(totalSumState);
Here, using the recoil will be the same as the useState hook, but instead of using useState we’ll be using useRecoilState and will pass the state that we need to use. Here we need to manage totalState so we’ll be passing totalSumState as a parameter to useRecoilState.
Core Features:
- Atoms: They are units of self-contained state that can read and update any component directly.
- Selectors: They create a derived state from the atoms and other selectors. They might be asynchronous, but they can handle all the side effects.
Pros:
- React Integration: Recoil was designed to work with React. It can efficiently handle the state changes in the application using the render cycle.
- Better Performance: Leveraging the custom graph-based data model, Recoil can become fast and efficient even when dealing with large and complex applications.
Cons:
- Steep Learning Curve: Learning to use Recoil is difficult in comparison to other state management libraries, especially for those unfamiliar with a graph-based data model.
Use Cases:
- Recoil consists of asynchronous updates and complex derived states. It offers selectors that work well with the concurrent mode and React suspense.
7.2 Redux
When compiling a list of React state management libraries, adding Redux is quite mandatory. Every potential state change has to be described through actions that are managed by multiple reducer functions. This would lead to a large amount of code, which is difficult to maintain. Redux was designed to solve this issue.
When using Redux for state management, you only need to dispatch an action object with a simple description of what to do. This action triggers the reducer function that returns a new state when given a previous state and an action object.
Although Redux is one of the mature state management libraries, it still has issues regarding the boilerplate. To mitigate this, the Redux Toolkit is a go-to solution. It adheres to the best practices, reduces the required boilerplate, and simplifies the store setup. Moreover, it comes with batteries included that enable you to make changes to the state easily. When working on async logic, you can use Redux-Thunk.
Further Reading on: Redux vs Redux Toolkit
API Example:
totalSumSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const totalSumSlice = createSlice({
name: 'totalSum',
initialState: { value: 0 },
reducers: {
addAmount: (state) => {
state.value += 5
},
removeAmount: (state) => {
state.value -= 5
},
},
})
// Action creators are generated for each case reducer function
export const { addAmount, removeAmount } = totalSumSlice.actions
export default totalSumSlice.reducer
Here we have to create a small portion of the store called reducer which will hold the value of the totalSum. CreateSlice will create a slice that holds the reducer (hold the value) and actions (update the value).
As the initial value, we have used 0 to start the sum, and addAmount will increase the state value by 5, and removeAmount will decrease the state value by 5.
Once created, we need to export the actions and reducer so it can be used in the application’s component.
store.js
import { configureStore } from '@reduxjs/toolkit'
import totalSumReducer from "./slice/totalSumSlice";
export default configureStore({
reducer: {
totalSum: totalSumReducer,
},
})
Here we need to configure the store for our application, which can hold multiple reducers.
configureStore will create a store in the application, which can then provide state access to different React components.
index.js
To access the store in the application, we need to provide the store using the Provider in the same way we use the context provider. This will make sure that each wrapped component will have access to the store.
App.js
import { useDispatch, useSelector } from "react-redux";
import { addAmount, removeAmount } from "src/store/slices/totalSumSlice";
function App() {
const totalSum = useSelector((state) => state.totalSum.value)
const dispatch = useDispatch()
const addSumAmount = () => dispatch(addAmount());
const removeSumAmount = () => dispatch(removeAmount());
return (
{totalSum}
);
}
To access the state value from the store, we need to use the useSelector hook. This will have access to the whole state object from which we only need to access the totalSum reducer, and from that, we only need the value variable. Which will be stored in the totalSum constant.
To call the action method that updates the state value can not be called directly because these are actions and not methods. So to call these actions, we need to pass them inside the dispatch method, which will call the addAmount action, which leads to the state update.
Core Features:
- State Tree: The state for the whole app is stored in a single JavaScript object called Store.
- Actions: Dispatched to trigger state changes, actions consist of the information on the changes to be made in the state.
- Reducers: They are JavaScript functions that manage the state changes according to the information provided by the action objects. Reducers return the updated state when provided with the existing state and an action.
Pros:
- Predictable State Management: Redux adheres to a strict pattern for state management. This makes it easy to understand how the state will change in response to an event or the actions of the users.
- Centralized State: Redux avoids prop drilling and easily manages states across various components because it manages all the states in a single store.
Cons:
- Overkill for Small Apps: Using Redux for small apps isn’t worth all the effort. Instead, you can use simple state management methods, such as context or local state.
Use Cases:
- Redux is very useful for large applications consisting of predictable state containers and a unidirectional flow of information. Redux helps reduce the complexities and avoid any confusion.
7.3 MobX
MobX is another best state management library with over 25k GitHub stars. It follows the object-oriented programming (OOP) paradigm and relies on the concept of observables. This makes it a unique library. “Anything that can be derived from the application state should be derived automatically.” is the core belief of MobX.
It tracks the changes in the state using observables and automatically re-renders the components. MobX also offers a single store to manage the app’s state and provides a set of rules, known as actions, to update the state.
API Example:
globalState.js
export function createTotalSum() {
return makeAutoObservable({
total: 0,
addAmount() {
this.total += 5
},
removeAmount() {
this.total -= 5
}
})
}
Like Recoil MobX also provides architectural freedom and gets freedom from boilerplate.
To create a state we can use the makeAutoObservable method, which returns the defined method that updates the state value and the actual state which holds the latest value.
To use this we need to call the createTotalSum method which will return an object that holds the state value and methods to manipulate the state.
const totalSum = createTotal()
For now, let’s use this on the index.js file and pass the value down to App.js using props as given below and access the state in App.js file
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { createTotal } from "./globalState";
const root = ReactDOM.createRoot(document.getElementById('root'));
const totalSum = createTotal();
root.render();
App.js
import './App.css';
import { observer } from "mobx-react-lite"
function App({totalSum}) {
const addSumAmount = () => totalSum.addAmount();
const removeSumAmount = () => totalSum.removeAmount();
return (
{totalSum.total}
);
}
export default observer(App);
Here we need to wrap the component that is using the state with observer from “mobx-react-lite” which triggers side effect (like rerender) on the state update.
Core Features:
- Observables: The state variables can be annotated as observables using MobX. This makes them reactive to changes.
- Computed Values: Computed values are automatically updated whenever the dependencies change, deriving new values from the state variables.
- Actions: They can also be used to explicitly change the observable state, to handle the side effects.
Pros:
- Simple API: Getting started with MobX is easy as it comes with a simple and intuitive API, especially if you have a working knowledge of React’s component model.
- Improved Performance: MobX is highly efficient and fast, even for large and complex apps because it utilizes a highly optimized reactive model.
Cons:
- Less Predictable: Due to its reactive model, it’s challenging to predict the state changes in response to events and user actions.
Use Cases:
- MobX is an ideal option for reactive apps where the states update automatically because it offers reactive state management. In addition to that, it is easy to work with as it automatically minimizes the boilerplate code.
8. Conclusion
If you want to build dynamic and interactive user interfaces in React, then state management is essential. It helps to ensure that the app stays in sync with the user interface. In this article, we discussed how to implement state management in React applications using various approaches.
Now, each of these approaches may have its pros and cons. Depending on your project requirements and preferences, you can pick a suitable approach to the React state management library.
Ensure you follow the best practices to avoid the challenges we discussed in this blog. Using the RSM tools and libraries can help simplify the process for you. If you have any further queries, drop them in the comments section below. We will get back to you with an appropriate solution.
FAQs
Which state management is best in React?
If you are looking for local state management, then useState from React, which is very popular. But if you require a global state solution, then the popular solutions are built-in Context API, MobX, and Redux. Pick an option that suits your requirements.
Why Redux for state management?
Redux is used for state management because it allows you to handle the state of your React application in a single place. Moreover, it helps you track and predict the state changes, enabling you to understand your application’s changes.
Why use React state management?
Components are used to build a React app. Their states are managed internally, which would be easy when the app has fewer components. But as the app scales up, managing states shared across various components becomes more complicated. To resolve this issue, you need to use React state management.
What are state management tools?
A state management library offers a few tools to create and change the data structure in case of an event or action. Some popular React state management tools include the useState, the useReducer, and the useContext.
Comments