State management is one of those decisions that feels low-stakes early in a project and high-stakes six months in. At AAA's dispute resolution platform, we inherited a React codebase with deeply prop-drilled state — filter configs, pagination state, and async data passed through 5–6 component layers. Refactoring to Zustand was one of the most impactful engineering decisions I made there.
The Problem With Deep Prop Drilling
Prop drilling isn't just an aesthetics problem. When state lives several layers up from where it's used, every intermediate component becomes a pass-through. Add a new field to that state and you touch files that conceptually have nothing to do with the feature. Worse, every re-render of the parent causes re-renders all the way down the tree — even for components whose relevant slice of state hasn't changed.
Why Zustand Over Redux or Context?
- Zero boilerplate — no actions, reducers, or action creators
- Fine-grained subscriptions — components only re-render when their specific slice changes
- TypeScript-first — store type is inferred from the create() call
- Works outside React — useful for utility functions that need to read state
- No Provider wrapper needed — global store is just a hook
Sliced Store Pattern
For larger stores, Zustand recommends a sliced pattern — each domain of state is defined in its own slice and combined into one store. This keeps files manageable without fragmenting state into multiple separate stores.
// filterSlice.ts
export type FilterSlice = {
filters: CaseFilters;
setFilter: (key: keyof CaseFilters, value: string) => void;
resetFilters: () => void;
};
const defaultFilters: CaseFilters = { status: "all", type: "all", dateRange: null };
export const createFilterSlice = (set: SetState): FilterSlice => ({
filters: defaultFilters,
setFilter: (key, value) =>
set((s) => ({ filters: { ...s.filters, [key]: value } })),
resetFilters: () => set({ filters: defaultFilters }),
});
// store.ts
export const useCaseStore = create<FilterSlice & PaginationSlice>()((...a) => ({
...createFilterSlice(...a),
...createPaginationSlice(...a),
}));Selectors Prevent Unnecessary Re-renders
The most important Zustand pattern for performance is using selectors. Rather than subscribing to the entire store, a component subscribes to only the specific fields it uses. Zustand compares the selector's output by reference equality on each state update, skipping the re-render if nothing changed.
// BAD — subscribes to entire store, re-renders on any change
const { filters, pagination } = useCaseStore();
// GOOD — only re-renders when filters.status changes
const status = useCaseStore((s) => s.filters.status);
// GOOD — selector for derived value
const activeFilterCount = useCaseStore(
(s) => Object.values(s.filters).filter(Boolean).length
);Results
After the refactor, the highest-churn screens (case filter panel + results table + pagination) went from being the buggiest to the most stable. Redundant re-renders were eliminated. New filter fields could be added in one file without touching intermediary components. And junior engineers could understand the state shape in seconds — just open the store file.
“The best state management solution is the one that makes state changes obvious and co-located with the components that care about them.”