Cartoon bear juggling state management concepts

Zustand: Simplifying React State Management

The Gray Cat
The Gray Cat

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!

Comments