Zustand for Lightweight React State
Create a Zustand store with create(), read state with the hook, update it with actions, and enjoy a minimal API without boilerplate.
Zustand for Lightweight React State is a free Frontend Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is Zustand?
Zustand is a minimal, fast state management library for React. The entire API surface is tiny: create a store, use a hook to read and update it. No reducers, no actions, no boilerplate.
Creating a Store
create() defines the store with state and actions as a plain object. The returned value is a hook.
import { create } from 'zustand';
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));Reading Store State
Call the hook with a selector function to subscribe to specific state slices. The component only re-renders when the selected slice changes.
function Counter() {
const count = useCounterStore(state => state.count);
return <p>Count: {count}</p>;
}Reading Actions
Select actions the same way. Actions don't change, so selecting them doesn't cause extra re-renders.
function Controls() {
const { increment, decrement } = useCounterStore(
state => ({ increment: state.increment, decrement: state.decrement })
);
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}Updating State with set()
The set() function merges updates shallowly (like setState in React class components). Pass a function for updates that depend on previous state.
// Merge:
set({ loading: true });
// Function for dependent updates:
set(state => ({ count: state.count + 1 }));
// Replace entirely (replace: true):
set({ count: 0, loading: false }, true);Async Actions
Actions in Zustand can be async — just call set() after awaiting the async operation.
export const useUsersStore = create<UsersStore>((set) => ({
users: [],
loading: false,
fetchUsers: async () => {
set({ loading: true });
const users = await fetch('/api/users').then(r => r.json());
set({ users, loading: false });
}
}));Slices — Composing Large Stores
Split a large store into slices (separate objects) and combine them. Zustand's pattern doesn't require the slice abstraction but it's a clean way to organise large stores.
Middleware: persist
The persist middleware saves Zustand state to localStorage and rehydrates on load — with minimal setup.
import { persist } from 'zustand/middleware';
export const useSettingsStore = create<Settings>(
persist(
(set) => ({
theme: 'light',
setTheme: (theme) => set({ theme })
}),
{ name: 'app-settings' } // localStorage key
)
);Middleware: devtools
Add Redux DevTools support to Zustand with the devtools middleware. You get full action logging and time-travel.
Zustand vs Redux Toolkit
Zustand: minimal, no boilerplate, fast to set up, great for small-to-medium apps. RTK: structured, scalable, best-in-class DevTools, better for large teams. Both are solid choices — Zustand's simplicity wins for most apps.
Selectors and Performance
Always select the minimum slice of state your component needs. Selecting the entire state object causes re-renders on any store change. Use shallow from zustand/shallow for multi-property selections.
import { shallow } from 'zustand/shallow';
const { theme, font } = useSettingsStore(
state => ({ theme: state.theme, font: state.font }),
shallow // compare each property shallowly
);Quick Check
How do you create a Zustand store that an entire React app can use?
Recap: Zustand
create() defines state and actions. The returned value is a hook. Select slices to avoid unnecessary re-renders. set() merges state updates. Async actions call set() after await. persist middleware for localStorage. devtools for Redux DevTools integration. No Provider needed.
Frequently asked questions
Is the “Zustand for Lightweight React State” lesson free?
Yes — the full text of “Zustand for Lightweight React State” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “Zustand for Lightweight React State”?
Create a Zustand store with create(), read state with the hook, update it with actions, and enjoy a minimal API without boilerplate. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Zustand for Lightweight React State” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Redux Toolkit: createSlice and configureStore
- Zustand for Lightweight React State
- Pinia for Vue: defineStore and storeToRefs
- When to Use Global vs Local State