In this article we are going to learn state management in React. This is an overview of the things that we are going to learn
If you are looking for a React Native Chat SDK to build in app chat messaging application, you can consider DeadSimpleChat React Native SDK
- Why state management is important in React
- When developing complex and large scale applications what are the state management issues faced by developers
- What are the built in state management solutions provided by React
- Popular State Management Libraries available today, including Redux, MobX, and Recoil
- How to implement advanced state management patterns and best practices
- Selecting specific state management techniques according to project requirement
Importance of state management in react
- How react applications handle data
- Importance of having consistancy when handling data
- How to imporve maintainability to react applications
- Utilizing best performance from react applications
State management issues when developing Complex and large scale applications
In this section we are going to look at some of the issues faced by developers when working on complex and large scale applications
Prop Drilling: Passing data form Parent component to child component through props becomes tiresome and difficult as the application grows.
In large heirarchial components passing props from parents to deeply nested child component through props is known as prop drilling.
You can use state management technologies like useContext and useRef or external state management libraries to mitigate this problem
Optimizing and Maintaing Performance as application Grows: Maintaining the consistance of state across multiple components becomes challenging when developing large scale applications
In this article we will explore technique on how to do this. Some of the methods are Optimizing state updates, avoiding unnessesory re renders and performance improvements with tools like React Devtools
Asynchronous state updates : Asynchronous state upadtes and side effects can sometimes introduces complexity and race conditions
With the help of state management libraries like Redux and middlewares like Sagas as well as Recoild Sync selectors we can mitigate the risks
Built In State Management tools: useState and useReducer
In this section we will be discussing the built in state management technologies in react name the useState and useReducer hooks
useState
It is a core hook in react, useState is used to manage state, with useState you get the state variable and the setState function that you can use update the state of a component
Here we are initializing the state count with the useState and setting the initial value to 0
then we are using the setCount funtion to update the state by 1 whenever the Add button is clicked
useReducer
usereducer is a state management hook that is used along with a reducer function and an initial state
this function will work well with state Objects having multiple properties and complex state structures
This is how the reducer function works
- The reducer function takes the current state and a dispatch function for invoking actions
- So, to reiterate It takes the current state and an action and return the updated state
- This pattern is similar to the recducers in Redux through it works locally inside a component
let us consider an example
In the above example, we have the initial state and a reducer function. We are providing both to the useReducer
hook. the reducer function here handles the state
Both useState and useReducer are state management tools in react
Popular state management patterns and what are their strengths and weaknesses
In this section we are going to consider what are some of the popular state management patterns and what are their strengrth and weaknesses
Lifting the state up
many times you want to share state between components, a possible solution to this is moving the state to a common parent component.
To enable the sibling components to share the state, a callback function provided by the parent.
This methodology is called as lifting the state up.
Strengths of the method: Lifting the state up
Built in pattern: Lifting the state up is a built in pattern in react and this is encouraged by react. thus does not require any external dependencies or libraries to function
Easy to use : By consolidating the shared state in a common parent and enabling the siblings to use the state
Sibling components can communicate : It provides a good way for the siblings to talk with one another without using advanced state management techniques
Weakness:
Prop Drilling: We studies prop drilling previously in this article. basically in component that have a large hierarchy it becomes difficult to pass data to child component nested deep inside the component tree
Scalability issues: This methodology cannot be scaled to large components because of issues like prop drilling and advanced state management techniques are required.
Local Component state
Encapsulation: With local component state, every component has its own state that is not shared with other components. This leads to a sence of encapsulation
Where each component stores the data that is relevant to it inside the component
Flexibility : With react hooks like useState
and useReducer
you can easily manage state and this provides flexibility both for simple as well as complex state management requirements
Performance: This methods avoids isssues like unnessessory re renders and other performance issues and keeps state management simple
Introducing Popular state management libraries
In this section we will briefly touch up the popular state management libraries and thier core concepts
Then in the next section we will dig deeper into these libraries
Redux:
Redux is a well know state management library based on the principles of single immutable state tree, actions, and reducers
Core Features
- State tree: A single Javascript Object called the store has the state for the entire application
- Actions: Actions are dispatched to trigger state changes. They contain a payload with information describing state changes
- Reducers: reducers are javascript functions. They handle state changes based on the information payload provided by the dispatched actions. When given the current state and an action they return the updated state
Use-Case: Redux is beneficial in large scale applications, where unidirectional flow on information and predictable state container are important so as not to increase complexcity and avoid confusion
One trade off is that there is an advanced learning curve when learning about redux and it is difficult and too much effort for smaller applicaitons
MobX
MobX is a reactive state management library, that has core concepts like
Observables, computed values and actions to automatically manage state updates
Core Features
Observables: Using MobX you can annotate state variables as Obsevables thus making them reactive to change
Computed Values: Computed values update automatically when the dependencies change and are derived from the state values
Actions: To manage side effects actions can be used to modify observable state explicitly
use cases
MobX is best if you like reactive applications where the state automatically updates and it automatically minimizes boilerplate code and are easy to work with
Trade-Off
MobX is not suited for very large applications and can create confusion when debugging and can obfuscate understanding of the underlying logic
It also relies on decorators which is a fairly new concept in javascript
Recoil
Recoil is a newer state management library and it too like react is developed by facebook.
Core Features
Atoms: Recoil has a concept of atoms. Atoms are basically units of self contained state and they can be read and updated directly by the component
Selectors: Selector generate derived state from atoms and other selectors. These can be asynchronous and can handle side effects easily
Use cases
Recoil has complex derived state and asynchronous state updates, It has selectors which work quite well with react suspense and concurrent mode
Trade offs
Recoil's ecosystem is new and less mature than Redux or MobX but never the less it is a good library and hopefully with time there will be adequte support for Recoil as well
Redux: how to with examples
Redux is known for unidirectional flow of data and predictability with regards to state management
This leads to easier state management in large scale applications
Core Concepts
Actions: Actions are plain JavaScript Objects containing 1. Payload with data and type
property which indicates which type of action is to be performed
// An example of an action in Redux
{
type: 'ADD_COUNT',
payload: 1,
}
Reducers:
Reducers are pure functions that can reciever the current state and an action, they return the new state
The reducer specifies how the state will update in response to an action
const initialState = { count: 0 };
// An example of a reducer in redux
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_COUNT':
return { ...state, count: state.count + action.payload };
default:
return state;
}
}
Store: Store holds the complete state of the application in redux. all the state is stored in one place which is called the store
You can create the store using the createStore
function and a reducer that manages the state updates
import { createStore } from 'redux';
import counterReducer from './counterReducer';
const store = createStore(counterReducer);
Example
First create a new react application with npx
npx create-next-app
then install the redux package
npm install redux react-redux --save
In this section we will be creating a counter application with Redux
- Create the action types, action creators and the reducer function
// actionTypes.js
export const INCREMENT_COUNT = 'INCREMENT_COUNT';
// actions.js
import { INCREMENT_COUNT } from './actionTypes';
export const incrementCount = (amount) => ({
type: INCREMENT_COUNT,
payload: amount,
});
// reducers/counterReducer.js
import { INCREMENT_COUNT } from '../actionTypes';
const initialState = { count: 0 };
export default function counterReducer(state = initialState, action) {
switch (action.type) {
case INCREMENT_COUNT:
return { ...state, count: state.count + action.payload };
default:
return state;
}
}
2. Then work on creating the Redux store and connect it to react like so. this way
// store.js
import { createStore } from 'redux';
import counterReducer from './reducers/counterReducer';
const store = createStore(counterReducer);
export default store;
// index.js
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
3. Creating the counter component and connecting it to the redux store like
// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { incrementCount } from './actions';
const Counter = () => {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(incrementCount(1))}>Increment</button>
</div>
);
};
export default Counter;
Middleware Performance Optimization and Redux DevTools
Middleware: There are these middleware functions. These functions can intercept dispatched actions before they reach the reducer function
You can perform various tasks with the middleware functions like
- logging in
- handling side effects
- modifiying the action
The popular middleware for Redux includes Redux Thunk and Redux Saga these can be used to handle asynchronous actions
// middleware example with redux
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
Performance Optimization
There are various ways to optimize the performance in Redux. these include
useSelector
: does a shallow comparison to avoid unnessesory rerendersReact.memo()
: Memorizes the functional componentsreselect
: library can be used to created memorized selectors for derived state computations
Redux DevTools
It is a browser extention that provides debugging capabilities, you can inspect dispatched actions, visualize application state and time travel in state history
You can use it like this
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
Mobx In Detail
MobX simplifies state management in react applications. It has concepts of Observables, computed values and actions.
Key concepts
Observables: Observables are state variables that are reactive. When Observables change the MobX re renders the component
Computed Values: These values are computed and are based on their dependencies. When dependencies change the computed values are rendered again. These values are sometimes cached to optimize the performance
Actions: Actions are plain javascript functions that can change Observable state values. These functions make state changes more predictable and can be used to handle side effects as well
Practical Example
install the MobX in your react application
npm install mobx mobx-react-lite --save
In this example we are going to create a todo application
- In the first step we need to set up a MobX store that will store our state
// todoStore.js
import { makeAutoObservable } from 'mobx';
class TodoStore {
todos = [];
get incompleteTodos() {
return this.todos.filter((todo) => !todo.completed);
}
constructor() {
makeAutoObservable(this);
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
toggleTodo(index) {
this.todos[index].completed = !this.todos[index].completed;
}
}
const todoStore = new TodoStore();
export default todoStore;
Then we need to create a TodoList Component and we connect this component to the MobX store that we just created
// TodoList.js
import React, { useState } from 'react';
import { observer } from 'mobx-react-lite';
import todoStore from './todoStore';
const TodoList = observer(() => {
const [text, setText] = useState('');
const handleAddTodo = () => {
todoStore.addTodo(text);
setText('');
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todoStore.todos.map((todo, index) => (
<li key={index}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => todoStore.toggleTodo(index)}
/>
{todo.text}
</li>
))}
</ul>
<p>Incomplete todos: {todoStore.incompleteTodos.length}</p>
</div>
);
});
export default TodoList;
And that it we have created a react application with state management done by MobX
Recoil in detail
Recoil is built by facebook which also built react. Recoil has introduced a number of concepts to handle complex and derived state management in react.
Recoil also provides performance improvements and aims to be minimal and flexible solution balancing state consistancy, performance and maintainability
Core Concepts: Atoms and Selectors
Atoms are independent units of state in recoil, these hold primitive values and hence can be thought of as basic building blocks of state in react
Individual atoms can be subscribed by components and when an atom updates the components that are subscribed to that atom are updated and this improves performance
Let us learn more with the help of an example. In this example we are going to be creating a user auth with Recoil
import { atom } from 'recoil';
const isAuthenticatedAtom = atom({
key: 'isAuthenticated',
default: false,
});
Selectors: Selectors are pure functions. They provide an efficient mechanism for managing complex and derived state.
These selectors derive their state from atoms or other selectors.
Recoil will automatically update selectors when their dependencies change and memorize their outputs for performance improvements
Example of a selector that derives a value from the auth state
import { selector } from 'recoil';
import { isAuthenticatedAtom } from './isAuthenticatedAtom';
const currentUserSelector = selector({
key: 'currentUser',
get: ({ get }) => {
const isAuthenticated = get(isAuthenticatedAtom);
if (isAuthenticated) {
return { name: 'John Doe', email: 'john@company.com' };
}
return null;
}
});
Recoil in complex react applications
there are custom recoil hooks available to manage state in complex and large scale applications with Recoil
useRecoilState
useRecoilValue
useSetRecoilState
these hooks let you read and write data to your state
Let us look at an application where we will create an auth app with recoil
import React from 'react';
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
import { isAuthenticatedAtom, currentUserSelector } from './state';
function App() {
return (
<RecoilRoot>
<UserAuthentication />
</RecoilRoot>
);
}
function UserAuthentication() {
const [isAuthenticated, setIsAuthenticated] = useRecoilState(isAuthenticatedAtom);
const currentUser = useRecoilValue(currentUserSelector);
const handleButtonClick = () => {
setIsAuthenticated(prevState => !prevState);
};
return (
<div>
<button onClick={handleButtonClick}>
{isAuthenticated ? 'Log Out' : 'Log In'}
</button>
{currentUser && <p>Welcome, {currentUser.name} ({currentUser.email})</p>}
</div>
);
}
export default App;
Async Selectors
Recoil also has support for async selectors that let you handle async actions like fetching data from an external APIs.
Async selectors can be used with react Suspense to fetch data and display it in your application and for a easy user experience
Here is an example os using async selectors
import { selector } from 'recoil';
const fetchUser = async (userId) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
};
const userSelector = selector({
key: 'user',
get: async ({ get }) => {
const userId = get(userAtom);
return userId ? await fetchUser(userId) : null;
}
});
Need Chat API for your website or app
DeadSimpleChat is an Chat API provider
- Add Scalable Chat to your app in minutes
- 10 Million Online Concurrent users
- 99.999% Uptime
- Moderation features
- 1-1 Chat
- Group Chat
- Fully Customizable
- Chat API and SDK
- Pre-Built Chat
Conclusion
In this article we learned about state management in react and what are some of the built in state management tools and what are some of the external libraries available for state management
We also learned how to use these libraries with examples and what are the use cases that fit best for each of these also where using the built in tools is the best solution
I hope you liked the article. Thank you for reading