Have you ever wished for a state management library that's as simple as useState
but works globally across your entire React application ? Are you looking for a state management library that doesn't require tons of boilerplate code
? Then this article is for you, it will show you how to handle React state using a simple external library called Zustand.
Indeed! State and state management have always been the backbone of React apps, determining when and how components re-render. While React's built-in useState
hook works perfectly for local component state, things get worse when you need to share state across multiple components.
As your React app grows and becomes more complex, you'll find yourself facing challenges like prop drilling
, context provider hell
, and the need for a cleaner way to manage state.
React State Management
You can manage the state of a React application in several ways.
1.Built-in React State Solutions: Native hooks like useState
, useReducer
, useContext
, and useMemo
provide foundational state management capabilities.
2.Indirect State Managers: There are external libraries such as React Router
and React Query
. They are not primarily used to manage the state. Yet, when combined with a native hook, they can handle the state well.
3.Dedicated State Management Libraries: Purpose-built solutions like Redux
, Zustand
, Jotai
, and Valtio
are designed exclusively for comprehensive state management across your application.
What is Zustand ?
Zustand
is a lightweight state management library. It is compact, fast, and scalable. It's simple with little boilerplate code. It doesn’t rely on a provider. As a result, you don’t have to code as much React logic, which may lessen the things we tend to forget. It works based on simplified flux principles and primarily makes use of hooks.
Why Zustand ?
It is faster than context. It lets you subscribe to specific slices of state, reducing unnecessary re-renders.
It does state merging by default. Consider updating a single property of an object state,
{name: 'John', age: 25}
. You can directly set{age: 26}
. Zustand will merge the data for you. You don’t have to distribute the old state and update properties like{…state, age: 26}
.It is extendable by default. Thus, you can use a variety of middleware types such as persist, immer, devtools ... etc.
-
It is less opinionated and rigid. You don’t have to stick to one way of doing things. Even though there is a recommended approach, you are not required to adopt it.
End of theory. Let's get your hands dirty now!
Zustand with ReactJs
Let's create a React project to show how to manage the states using Zustand. Let's consider a simple Counter application to understand Zustand better. The steps are listed below.
1. Create a React application
Create your React application using the below command.
npm create vite@latest 'project-name'
2. Install Zustand Dependency
Go to the project’s directory and install the zustand dependency to manage the React state.
npm install
npm i zustand
3. Let's Start
- Let’s first build a basic counter using React's local state to understand the problem Zustand solves.
Let's create a Counter application with simple increase and decrease button as seen in the below image.
refer below code.
//App.jsx
import { useState } from 'react'
import './App.css'
function App() {
return (
<div>
<Counter/>
</div>
)
}
function Counter(){
const [count, setCount] = useState(0)
return(
<div>
<CurrentCount count={count}/>
<br />
<div className='flex'>
<Increase setCount={setCount}/>
<Decrease setCount={setCount}/>
</div>
</div>
)
}
function CurrentCount({count}){
return(
<div className='text-8xl'>
{count}
</div>
)
}
function Decrease({setCount}){
return(
<div className='m-3'>
<button onClick={()=>{setCount(c=>c-1)}}>Decrease Count</button>
</div>
)
}
function Increase({setCount}){
return(
<div className='m-3'>
<button onClick={()=>{setCount(c=>c+1)}}>Increase Count</button>
</div>
)
}
export default App
- Upon clicking either of the increase or decrease button, only the count part should re-render, but this is not the case here, on clicking either of the buttons, the count, both of the button and the whole app component re-renders unnecessarily, as seen in below images.
- So, now let's use Zustand to manage the state and resolve this issue.
4. Using Zustand to Fix Re-Renders
Let's create a store. Refer the below code to create Store.jsx
file.
import { create } from "zustand";
export const useCounterStore = create((set)=>({
count:0,
increment:()=>{
set((state)=>({count:state.count+1}))
},
decrement:()=>{
set((state)=>({count:state.count-1}))
}
}))
Zustand store is a hook, which is why
useCounterStore
is the component name.create
is the method used to create the store. Thestore
is the sole source of truth that each component shares. The functionset
is used to modify the state of a variable or object.The state object of the store in the example contains the following field and methods:
count
, which stores the current count of the counter andincrement
anddecrement
methods which increase and decrease the count respectively.The
increment
anddecrement
methods useset
to modify the state of the count.
4. Bind the component with your store
Let's modify the code inside App.jsx
using the useCounterStore
.
import './App.css'
import { useCounterStore } from './store'
function App() {
return (
<div>
<CurrentCount/>
<Buttons/>
</div>
)
}
function CurrentCount(){
const currcount=useCounterStore((state)=>state.count);
return(
<div className='text-8xl'>
{currcount}
</div>
)
}
function Buttons(){
const increase =useCounterStore((state)=>state.increment);
return (
<div className='flex'>
<div className='m-3'><button onClick={increase}>Increase</button></div>
<div className='m-3'><button onClick={useCounterStore((state)=>state.decrement)}>Decrease</button></div>
</div>
)
}
export default App
- The
App.jsx
contains three components i.e.App
,CurrentCount
andButtons
. - The
App
is the parent component which contains both theCurrentCount
andButtons
components. - The
CurrentCount
component subscribes only tocount
from theuseCounterStore
. - The
Buttons
component subscribes only to theincrement
anddecrement
methods and does not re-render when the value of count changes.
Now, on clicking either of the increase or decrease button, only the count part re-renders and there are no unnecessary re-renders, as seen in below images.
Neither of the button's re-render unnecessarily when the value of count changes.
Since the Zustand store is a
hook
, you can use it anywhere. In contrast to Redux or Redux Toolkit,no context provider
is required. Simply select your state, and the component will re-render when the state changes. We must give a selector to theuseCounterStore
to get only the desired slice.
We are not done yet! Zustand has a big advantage: Middlewares.
which we'll be covering in the upcoming blog.
Summary
As we mentioned in this article, state management is critical in React applications. As your application grows, you will have to pick a strong way to manage its state. The Zustand library is an ideal remedy for managing your React state. It is much simpler and has less boilerplate than Redux. You can also explore different state management libraries to find the right one for your application.
Hi Dev.to contributors! If you've published on Dev.to, read this: We're offering DEV Contributor rewards to reward our amazing writing community. Claim your rewards here (instant distribution). – Admin