Managing State with Zustand: A Lightweight Alternative to Redux
Zustand combiens the simplicity of React hooks with the state management capabilities of Redux. The create() function returns an object that serves as both a hook and a store, eliminating the need for separate providers and reducing boilerplate significantly.
Architecture Overview
┌─────────────────────────────┐
│ create() Return Value │
│ (Both Hook and Store) │
└──────────────┬──────────────┘
┌─────────────┐ ┌─────────────┐
│ React Hook │ │ Store Object│
│ useStore() │ │getState() │
│ (in components)│ │setState() │
│ subscribes │ │subscribe() │
└─────────────┘ └─────────────┘
Redux Implementation
Redux requires multiple imports and a structured approach with reducers, actions, and a Provider wrapper:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
const operationMap = {
INCREMENT: (s) => ({ count: s.count + 1 }),
DECREMENT: (s) => ({ count: s.count - 1 }),
};
function counterReducer(state = { count: 0 }, action) {
const handler = operationMap[action.type];
return handler ? handler(state) : state;
}
const store = createStore(counterReducer);
function Counter() {
const count = useSelector((s) => s.count);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<span>{count}</span>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<Counter />
</Provider>
);
Custom Hook Approach
React hooks provide local state management but lack shared state across components:
import React, { useState } from 'react';
function useCounter() {
const [count, setCount] = useState(0);
const increase = () => setCount((c) => c + 1);
const decrease = () => setCount((c) => c - 1);
return { count, increase, decrease };
}
function Counter() {
const { count, increase, decrease } = useCounter();
return (
<div>
<button onClick={decrease}>-</button>
<span>{count}</span>
<button onClick={increase}>+</button>
</div>
);
}
export default Counter;
Zustand Implementation
Zustand provides a minimal API that cmobines the best of both approaches:
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
export default Counter;
Accessing State Inside and Outside Components
// Inside components - uses hook (auto-subscribes and triggers re-renders)
const count = useCounterStore((state) => state.count);
// Outside components - uses getState() (no re-renders)
const count = useCounterStore.getState().count;
Full Example with External State Access
import React from 'react';
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
reset: () => set({ count: 0 }),
}));
function Counter() {
const count = useStore((s) => s.count);
const increment = useStore((s) => s.increment);
const reset = useStore((s) => s.reset);
return (
<div>
<div>Count: {count}</div>
<button onClick={increment}>+</button>
<button onClick={reset}>Reset</button>
</div>
);
}
function externalIncrement() {
const currentValue = useStore.getState().count;
console.log('Current count:', currentValue);
useStore.setState((s) => ({ count: s.count + 1 }));
}
export default function App() {
return (
<>
<Counter />
<button onClick={externalIncrement}>External +</button>
</>
);
}