Zustand

npm install immer
npm install zustand
  • Much less boilerplate than React Redux with same benefits over useContext

    • Selectively updates components, easy slices

Basics

  • like useSelector, updates based on strict equality

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)
    const increasePopulation = useStore((state) => state.increasePopulation)
  return <h1>{bears} around here...</h1>
}

Slices Pattern With Immer

  • Immer makes nested updates easier

    • IE, Since set(() => ({bears: {a: 0})) is shallowly merged, it would override other parts of {bears} so you would need set((state) => ({bears: {...state.bears, a: 0}}))

    • Immer makes it `set((state) => state.bears.a = 0)

store/userSlice.ts

import { StateCreator } from 'zustand';

import { User } from '@/API';
import type { AllSlices } from './index';

export interface UserSlice {
  user: User;
  setUser: (user: User) => void;
}

export const createUserSlice: StateCreator<AllSlices, [['zustand/immer', never]], [], UserSlice> = (
  set
) => ({
  user: null,
  setUser: (user: User) =>
    set((state) => {
      state.user = user;
    }),
});

store/index.ts

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

import { createUserSlice, UserSlice } from './userSlice';

//To add type & UserSlice with the newSlice
export type AllSlices = UserSlice;

export const useAppStore = create<AllSlices>()(
  immer((...args) => ({
    ...createUserSlice(...args),
  }))
);

Advanced

Slices

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

export const createBearSlice = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

import { create } from 'zustand'

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))

Nonhook update

const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))

// Getting non-reactive fresh state
const paw = useDogStore.getState().paw
// Listening to all changes, fires synchronously on every change
const unsub1 = useDogStore.subscribe(console.log)
// Updating state, will trigger listeners
useDogStore.setState({ paw: false })
// Unsubscribe listeners
unsub1()

Persist Middleware

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

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    },
  ),
)
  • Could autogenerate selectors to turn const bears = useBearStore((state) => state.bears) into the much? better const bears = useBearStore.use.bears()

Last updated