logo
eng-flag

Zustand Cheatsheet

Table of Contents

  1. Installation
  2. Basic Usage
  3. Creating a Store
  4. Using the Store
  5. Updating State
  6. Async Actions
  7. Middleware
  8. Persist Middleware
  9. Devtools
  10. TypeScript Usage
  11. Selectors
  12. Combining Stores
  13. React Hooks
  14. Best Practices
  15. Testing

Installation

To install Zustand in your project:

npm install zustand
# or
yarn add zustand

Basic Usage

Here's a basic example of creating and using a Zustand store:

import create from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

function Controls() {
  const increasePopulation = useStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

Creating a Store

Create a store with initial state and actions:

import create from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}))

Using the Store

Use the store in your components:

function Counter() {
  const { count, increment, decrement, reset } = useStore()

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

Updating State

Update state partially or completely:

const useStore = create((set) => ({
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
  updateName: (firstName, lastName) => set({ firstName, lastName }),
  incrementAge: () => set((state) => ({ age: state.age + 1 })),
}))

Async Actions

Handle asynchronous actions:

const useStore = create((set) => ({
  users: [],
  fetchUsers: async () => {
    const response = await fetch('https://api.example.com/users')
    const users = await response.json()
    set({ users })
  },
}))

Middleware

Add middleware to your store:

import create from 'zustand'

const myMiddleware = (config) => (set, get, api) =>
  config(
    (...args) => {
      console.log('  applying', args)
      set(...args)
      console.log('  new state', get())
    },
    get,
    api
  )

const useStore = create(
  myMiddleware((set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  }))
)

Persist Middleware

Use persist middleware to save state to localStorage:

import create from 'zustand'
import { persist } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      fishes: 0,
      addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
    }),
    {
      name: 'food-storage', // unique name
      getStorage: () => localStorage, // (optional) by default, 'localStorage' is used
    }
  )
)

Devtools

Enable Redux DevTools:

import create from 'zustand'
import { devtools } from 'zustand/middleware'

const useStore = create(devtools((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
})))

TypeScript Usage

Use Zustand with TypeScript:

import create from 'zustand'

interface BearState {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

const useStore = create<BearState>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

Selectors

Use selectors for efficient updates:

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

Combining Stores

Combine multiple stores:

const useBearStore = create((set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
}))

const useFishStore = create((set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
}))

const useStore = create((set) => ({
  ...useBearStore(set),
  ...useFishStore(set),
}))

React Hooks

Create custom hooks with Zustand:

import { useCallback } from 'react'
import create from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

export const useCounter = () => {
  const { count, increment } = useStore()
  const incrementAsync = useCallback(() => {
    setTimeout(increment, 1000)
  }, [increment])

  return { count, increment, incrementAsync }
}

Best Practices

  1. Keep stores small and focused
  2. Use selectors for better performance
  3. Avoid storing derived state
  4. Use middleware for cross-cutting concerns
  5. Leverage TypeScript for type safety
  6. Use immer for easier state updates

Example of using immer:

import create from 'zustand'
import produce from 'immer'

const useStore = create((set) => ({
  todos: [],
  addTodo: (todo) =>
    set(
      produce((state) => {
        state.todos.push(todo)
      })
    ),
}))

Testing

Test your Zustand stores:

import { create } from 'zustand'
import { act } from 'react-dom/test-utils'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

describe('useStore', () => {
  it('should increment counter', () => {
    const { result } = renderHook(() => useStore())

    act(() => {
      result.current.increment()
    })

    expect(result.current.count).toBe(1)
  })
})

2024 © All rights reserved - buraxta.com