zustand is a compact option for shared React state. Its create() function returns a hook that components can call wherever they need the same state and actions. Unlike a reducer-and-context setup, this does not require a context provider or consumer hook. That simplicity makes it attractive when a data layer needs shared state without much wiring. section 8.6 section 8.6.1
A goal-tracking data layer shows the main implementation choices:
- Define state values and update callbacks together in useData. Wrap the store with immer middleware to write updates against a draft, and with persist middleware to save data to local storage. section 8.6.1 section 8.6.2
- Expose focused hooks that select only what their callers need. For example, useAddThing selects the addThing action, while useCurrentThing selects the current item. section 8.6.2
- Keep a DataProvider only if an interchangeable data-layer interface requires it; in this variant, it does not distribute state. section 8.6.2
Listing 8.25 src/data/useData.js
import { create } from "zustand";
import { v4 as uuid } from "uuid";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
export const useData = create( #1
persist( #2
immer((set) => ({ #2
things: [],
currentThing: null,
seeThing: (newThing) =>
set((draft) => {
draft.currentThing = newThing;
}),
seeAllThings: () =>
set((draft) => {
draft.currentThing = null;
}),
addThing: (name) =>
set((draft) => {
draft.things.push({ id: uuid(), name, done: [] });
}),
removeThing: (id) =>
set((draft) => {
const index = draft.things.findIndex((thing) => thing.id === id);
if (index !== -1) {
draft.things.splice(index, 1);
if (id === draft.currentThing) {
draft.currentThing = null;
}
}
}),
doThing: (id) =>
set((draft) => {
const thing = draft.things.find((thing) => thing.id === id);
thing.done.push(Date.now());
}),
undoThing: (id, index) =>
set((draft) => {
const thing = draft.things.find((thing) => thing.id === id);
thing.done.splice(index, 1);
}),
})),
{ name: "100-things-zustand" }
)
);
The useData listing shows create() wrapping persist and immer around a store of goal-tracking values and actions. section 8.6.2
Choose zustand for its low setup cost, not because every application needs another state library. React context with useReducer can go far, and Redux Toolkit remains an alternative. zustand offers fewer built-in patterns for organizing multiple unrelated stores, which can make larger systems harder to structure. It can also have a narrower job: when server data is handled through queries and mutations, a zustand hook can hold only the local UI choice between list and detail view. section 8.9 section 9.3.3
