Zustand: State Management Without Overengineering
September 24, 2026 Avishka Devinda
September 24, 2026 Avishka Devinda
State management can become more complicated than the feature it is supposed to support.
I have worked on projects where a simple boolean eventually lived inside a large global store with actions, reducers, selectors, and multiple files.
These days, I try to keep state local for as long as possible.
When I actually need shared client state, I often reach for Zustand.
My first option is still React state.
If only one component needs a value, I use useState.
If a parent and a few children need it, I usually pass props or use a small context.
I do not create a global store just because a project has Zustand installed.
Global state is useful when the same client-side state is needed across separate parts of the interface.
A basic store can stay very small:
import { create } from 'zustand';
type SidebarStore = {
open: boolean;
setOpen: (open: boolean) => void;
};
export const useSidebarStore = create<SidebarStore>((set) => ({
open: false,
setOpen: (open) => set({ open }),
}));
Then a component can select only what it needs:
const open = useSidebarStore((state) => state.open);
const setOpen = useSidebarStore((state) => state.setOpen);
That is one of the things I like about Zustand. The store does not need much ceremony.
I use Zustand for things like:
These are values that are genuinely shared by different components.
I try not to use Zustand as a replacement for every other type of state.
For example, server data usually belongs with the server data layer.
If I load projects from a database, I do not automatically copy the entire response into Zustand.
That can create two sources of truth:
Now I have to keep them synchronized.
For server data, I would rather use Server Components, normal data fetching, or a dedicated server-state tool if the application needs one.
I prefer multiple small stores over one giant application store.
For example:
useThemeEditorStore
useBuilderStore
useDashboardFiltersStore
is easier for me to understand than:
useAppStore
with fifty unrelated values.
A focused store makes it clear why the state is global.
This is important for keeping components predictable.
Instead of reading the whole store:
const store = useBuilderStore();
I prefer selecting the exact state:
const selectedBlock = useBuilderStore(
(state) => state.selectedBlock
);
It makes the dependency obvious and can avoid unnecessary updates.
Zustand can persist state to browser storage.
That is useful for things like:
But I do not store sensitive information there.
Browser storage is not a secure place for secrets, access tokens, or private server data.
Persistence should improve the user experience, not become an authentication system.
In Next.js, I keep Zustand on the client side.
I do not use a client store to replace Server Components.
A pattern I like is:
Server Component
↓
load initial data
↓
Client Component
↓
Zustand for interactive local/shared state
The server handles data that belongs on the server.
Zustand handles interactive state that belongs in the browser.
I like Zustand because it does not ask me to redesign the entire application around state management.
It gives me a store when I need a store.
The bigger lesson for me is not "use Zustand everywhere."
It is use global state only when the state is actually global.
When I follow that rule, Zustand stays small, useful, and easy to maintain.