Zustand: Simplifying React State Management
Zustand: Simplifying React State Management with TypeScript
Are you tired of wrestling with complex state management libraries in your React projects? Say hello to Zustand, a small, fast, and scalable state management solution that brings simplicity back to your React applications. In this article, we’ll explore how Zustand can make your life easier, from basic usage to more advanced scenarios, all with TypeScript support.
What is Zustand?
Zustand (German for “state”) is a minimalist state management library for React. It provides a straightforward API based on hooks, allowing you to create and use global state without the need for context providers or excessive boilerplate. Zustand’s simplicity doesn’t come at the cost of power – it handles common pitfalls like the zombie child problem and React concurrency with ease.
Installation
Getting started with Zustand is as simple as installing the package:
npm install zustand
Or if you prefer yarn:
yarn add zustand
Basic Usage
Let’s dive into how to use Zustand with a simple example. Imagine we’re building an app to track the bear population in a forest.
Creating a Store
First, we’ll create a store to manage our bear-related state:
import { create } from 'zustand'
interface BearState {
bears: number
increasePopulation: () => void
removeAllBears: () => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
In this example, we’ve created a store with:
- A
bears
state to keep track of the number of bears - An
increasePopulation
action to add a bear - A
removeAllBears
action to reset the bear count
Using the Store in Components
Now, let’s use our store in React components:
import React from 'react'
import { useBearStore } from './store'
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} bears in the forest</h1>
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>Add a bear</button>
}
That’s it! No need for providers, no complex setup. Just create your store and use it in your components.
Advanced Usage
Zustand’s simplicity doesn’t mean it lacks advanced features. Let’s explore some more powerful use cases.
Async Actions
Zustand handles async actions with ease. Let’s add a function to fetch bear data from an API:
interface BearState {
bears: number
fetchBears: (forestId: string) => Promise<void>
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
fetchBears: async (forestId: string) => {
const response = await fetch(`/api/bears/${forestId}`)
const bears = await response.json()
set({ bears })
},
}))
You can call this function from your components just like any other action:
import React, { useEffect } from 'react'
import { useBearStore } from './store'
interface BearDataProps {
forestId: string
}
function BearData({ forestId }: BearDataProps) {
const fetchBears = useBearStore((state) => state.fetchBears)
useEffect(() => {
fetchBears(forestId)
}, [forestId, fetchBears])
// ... rest of the component
}
Using Middleware
Zustand supports middleware to extend its functionality. Let’s look at two popular middleware options: persist and devtools.
Persist Middleware
The persist middleware allows you to save your store’s state to localStorage (or any other storage solution):
import { create } from 'zustand'
import { persist, PersistOptions } from 'zustand/middleware'
interface BearState {
bears: number
addBear: () => void
}
type BearPersist = (
config: StateCreator<BearState>,
options: PersistOptions<BearState>
) => StateCreator<BearState>
const useBearStore = create<BearState>()(
persist(
(set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
}),
{
name: 'bear-storage', // unique name
storage: localStorage, // (optional) by default, 'localStorage' is used
}
) as BearPersist
)
Now, your bear count will persist even when the user refreshes the page!
Redux DevTools
For debugging, you can connect your Zustand store to Redux DevTools:
import { create } from 'zustand'
import { devtools, DevtoolsOptions } from 'zustand/middleware'
interface BearState {
bears: number
addBear: () => void
}
const useBearStore = create<BearState>()(
devtools(
(set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
}),
{ name: 'Bear Store' } as DevtoolsOptions
)
)
This allows you to inspect your state changes in the Redux DevTools browser extension.
Combining Multiple Stores
While Zustand encourages creating small, focused stores, you might sometimes want to combine multiple stores. Here’s how you can do it:
import { create } from 'zustand'
interface BearState {
bears: number
addBear: () => void
}
interface FishState {
fishes: number
addFish: () => void
}
interface EcosystemState extends BearState, FishState {
feedBear: () => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
}))
const useFishStore = create<FishState>()((set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
}))
const useEcosystemStore = create<EcosystemState>()((set) => ({
...useBearStore.getState(),
...useFishStore.getState(),
feedBear: () => {
useBearStore.getState().addBear()
useFishStore.getState().addFish()
set({}) // Trigger update in the combined store
},
}))
Now you have a combined useEcosystemStore
that includes both bears and fishes!
Conclusion
Zustand offers a refreshing approach to state management in React applications, with excellent TypeScript support. Its simplicity makes it easy to get started, while its flexibility allows it to scale with your application’s needs. Whether you’re building a small project or a large-scale application, Zustand provides the tools you need to manage your state effectively.
By leveraging Zustand’s hooks-based API, middleware support, and ability to handle async actions, you can create clean, maintainable, and powerful state management solutions with full type safety. Give Zustand a try in your next React project – you might find that state management doesn’t have to be complicated after all!
Remember, the best state management solution is the one that fits your project’s needs and your team’s preferences. Zustand’s low learning curve, high flexibility, and strong TypeScript integration make it an excellent choice for many React applications. Happy coding!