Performance: Where Zustand Shines
Object Creation in Selectors
Wrong: Creating New Objects Every Render
// DON'T DO THIS! Re-renders on EVERY state change
function UserProfile() {
const { user, setUser } = useStore(state => ({
user: state.user,
setUser: state.setUser,
}))
return <div>{user.name}</div>
}
Why this breaks: `{user: ..., setUser: ...}` creates a new object reference every time. React compares via `===`, sees different objects, assumes state changed, triggers re-render.Right: Separate Selectors or useShallow
// Option 1: Separate selectors (BEST for most cases)
function UserProfile() {
const user = useStore(state => state.user)
const setUser = useStore(state => state.setUser)
return <div>{user.name}</div>
}
// Option 2: useShallow for multiple values
import { useShallow } from 'zustand/react/shallow'
function UserProfile() {
const { user, setUser } = useStore(
useShallow(state => ({
user: state.user,
setUser: state.setUser,
}))
)
return <div>{user.name}</div>
}
Granular Selectors
// BAD: Component re-renders on ANY state change
function Header() {
const store = useStore()
return <h1>{store.user.name}</h1>
}
// GOOD: Re-renders only when user.name changes
function Header() {
const userName = useStore(state => state.user.name)
return <h1>{userName}</h1>
}
Computed Values in Selectors
// BAD: slowFunction runs on EVERY state update
const Component = () => {
const result = useStore(state => slowFunction(state.value))
return <div>{result}</div>
}
// BETTER: Extract value, memoize separately
const Component = () => {
const value = useStore(state => state.value)
const result = useMemo(() => slowFunction(value), [value])
return <div>{result}</div>
}
// BEST: Lightweight computation in selector
const Component = () => {
const hasItems = useStore(state => state.items.length > 0)
return <div>{hasItems ? 'Has items' : 'Empty'}</div>
}
Custom Equality Functions
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'
const useStore = createWithEqualityFn<State>()(
(set) => ({
todos: [],
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: uuid(), text }]
})),
}),
shallow
)
// Or per-selector
function TodoCount() {
const todoCount = useStore(
state => state.todos.filter(t => !t.done).length,
(a, b) => a === b // Custom equality
)
return <span>{todoCount} remaining</span>
}
DevTools Integration
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const useStore = create<State>()(
devtools(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }),
false, // Don't replace state
'increment' // Action name in DevTools
),
}),
{ name: 'MyStore' } // Store name in DevTools
)
)
DevTools Features
- Time-travel debuggingRewind/replay state changes
- Action historySee every state mutation
- State inspectionExamine current state structure
- Action dispatchTrigger actions manually
Pro tip: Name your actions in `set()` calls for better debugging:
set(
(state) => ({ count: state.count + 1 }),
false,
'increment/by-one' // Shows in DevTools
)
Testing Strategies
Testing Pure Store Logic
// store.ts
export const createStore = () => create<State>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// store.test.ts
import { createStore } from './store'
describe('Counter Store', () => {
it('increments count', () => {
const store = createStore()
expect(store.getState().count).toBe(0)
store.getState().increment()
expect(store.getState().count).toBe(1)
})
})
Testing with React Components
import { renderHook, act } from '@testing-library/react'
test('updates state', () => {
const { result } = renderHook(() => useStore())
expect(result.current.count).toBe(0)
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
Mocking for Component Tests
// Mock the store for component testing
jest.mock('./store', () => ({
useStore: jest.fn(),
}))
test('renders user name', () => {
useStore.mockReturnValue({ user: { name: 'Alice' } })
render(<UserProfile />)
expect(screen.getByText('Alice')).toBeInTheDocument()
})
Performance Monitoring
Measure Re-renders
function PerformanceMonitor() {
const renderCount = useRef(0)
useEffect(() => {
renderCount.current++
console.log(`Component rendered ${renderCount.current} times`)
})
const count = useStore(state => state.count)
return <div>Count: {count}</div>
}
React DevTools Profiler
- Install React DevTools browser extension
- Click "Profiler" tab
- Record interaction
- Analyze which components re-rendered unnecessarily
Explore project snapshots or discuss custom web solutions.
Best Practices Checklist
DO
- Use granular selectors
- Separate subscriptions for different state pieces
- Name actions in DevTools
- Test store logic separately from components
- Use `useShallow` for multi-value selections
- Keep selectors lightweight
DON'T
- Create objects in selectors without `useShallow`
- Run expensive computations in selectors
- Subscribe to entire store unless necessary
- Forget to memoize heavy calculations
- Use deep equality checks without good reason
Performance Metrics
- Legacy approaches: ~280ms update time
- Modern hooks with memoized selectors: ~95ms
- Selective subscriptions: ~45ms
Premature optimization is the root of all evil. But knowing how to optimize is the path to enlightenment.
Thank You for Spending Your Valuable Time
I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Frequently Asked Questions
No. Use it only when selecting multiple values as an object. For single values, standard selectors are perfect and more performant.
Add console.log in selectors and use React DevTools Profiler. The "why-did-you-render" library also helps identify unnecessary re-renders.
Absolutely! `React.memo` and Zustand selectors work great together for maximum optimization. Zustand handles state subscriptions, `memo` prevents prop-based re-renders.
Minimal in development, but remove it in production builds. Use environment checks: `process.env.NODE_ENV === 'development' ? devtools(...) : (...)`.
Mock each store separately, or create a test wrapper that provides mock stores. Consider exporting unwrapped stores for easier testing.
Comments are closed