Zustand Performance Optimization: Selectors, Re-renders, and DevTools

  • Home
  • Zustand
  • Zustand Performance Optimization: Selectors, Re-renders, and DevTools
Front
Back
Right
Left
Top
Bottom
PERFORMANCE
Where You Can Stumble

Performance: Where Zustand Shines

Zustand’s automatic performance optimization is legendary—but only if you use it correctly. As Philipp Raab notes, Zustand limits re-renders to only components subscribed to particular state pieces, but incorrect selector patterns can sabotage this advantage.

This blog reveals the performance secrets that separate fast apps from slow ones.
KILLER
The #1 Performance Killer

Object Creation in Selectors

Wrong: Creating New Objects Every Render
Copy to clipboard
// 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
Copy to clipboard
// 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>
}
The Zustand documentation on `useShallow` explains it returns a memoized version of selectors using shallow comparison for memoization.
#01
Pattern 01

Granular Selectors

Subscribe only to what you need:
Copy to clipboard
// 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>
}
#02
Pattern 02

Computed Values in Selectors

Don’t slow down state updates with expensive computations:
Copy to clipboard
// 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>
}
#03
Pattern 03

Custom Equality Functions

For complex selections, provide custom equality:
Copy to clipboard
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
Debug Like a Pro

DevTools Integration

Copy to clipboard
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
Pro tip: Name your actions in `set()` calls for better debugging:
Copy to clipboard
set(
  (state) => ({ count: state.count + 1 }),
  false,
  'increment/by-one' // Shows in DevTools
)
STRATEGIES

Testing Strategies

Testing Pure Store Logic
Copy to clipboard
// 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
Copy to clipboard
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
Copy to clipboard
// 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

Performance Monitoring

Measure Re-renders
Copy to clipboard
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

Explore project snapshots or discuss custom web solutions.

BEST

Best Practices Checklist

DO
DON'T

Performance Metrics

According to “State Management in 2025” by Hijazi, in production applications with 50+ form fields:
Zustand’s selective re-rendering approach delivers these optimized results out of the box when used correctly.

Premature optimization is the root of all evil. But knowing how to optimize is the path to enlightenment.

Donald Knuth, The Art of Computer Programming

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!
Front
Back
Right
Left
Top
Bottom
FAQ's

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