Skip to main content
Zustand provides multiple ways to read state from your stores, each optimized for different use cases.

Using the Hook Without Selectors

The simplest way to read state is to call the hook without any arguments:
import { create } from 'zustand'

type BearStore = {
  bears: number
  fish: number
}

const useBearStore = create<BearStore>()((set) => ({
  bears: 0,
  fish: 0,
}))

function BearCounter() {
  // Gets the entire state object
  const state = useBearStore()
  return <h1>{state.bears} bears and {state.fish} fish</h1>
}
Reading the entire state object will cause your component to re-render on every state change, even if the component only uses a small part of the state.

Using Selectors

Selectors allow you to pick specific pieces of state, causing re-renders only when that specific data changes.

Basic Selectors

function BearCounter() {
  // Only re-renders when bears changes
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} bears around here...</h1>
}

function FishCounter() {
  // Only re-renders when fish changes
  const fish = useBearStore((state) => state.fish)
  return <h1>{fish} fish in the pond</h1>
}
Selectors use Object.is for equality checking by default. The component only re-renders if the selected value changes.

Selecting Multiple Values

The recommended approach is to use multiple hooks:
function Controls() {
  const bears = useBearStore((state) => state.bears)
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  
  return (
    <div>
      <p>Bears: {bears}</p>
      <button onClick={increasePopulation}>Add bear</button>
    </div>
  )
}
This pattern is efficient because Zustand’s hook is optimized for multiple calls in the same component.

Computed Selectors

Selectors can compute derived state:
type TodoStore = {
  todos: Array<{ id: number; done: boolean; text: string }>
  addTodo: (text: string) => void
  toggleTodo: (id: number) => void
}

const useTodoStore = create<TodoStore>()((set) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: Date.now(), done: false, text }],
    })),
  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      ),
    })),
}))

function TodoStats() {
  // Computed value: only re-renders when the count changes
  const completedCount = useTodoStore((state) =>
    state.todos.filter((todo) => todo.done).length
  )
  
  const totalCount = useTodoStore((state) => state.todos.length)
  
  return (
    <div>
      {completedCount} of {totalCount} completed
    </div>
  )
}

Using getState()

For reading state outside of React components or without subscribing to updates:
const useBearStore = create<BearStore>()((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}))

// Read state without subscribing
const currentBears = useBearStore.getState().bears

// Call actions outside components
useBearStore.getState().increasePopulation()
getState() does not subscribe to updates. It’s a one-time read of the current state. Use this for actions or event handlers, not for rendering.

Common Use Cases for getState()

const logCurrentState = () => {
  const { bears, fish } = useBearStore.getState()
  console.log(`Bears: ${bears}, Fish: ${fish}`)
}

document.getElementById('log-btn')?.addEventListener('click', logCurrentState)

Selector Performance

Memoizing Selectors

Avoiding Unnecessary Re-renders

1

Select only what you need

// Good: only re-renders when count changes
const count = useStore((state) => state.count)

// Bad: re-renders on any state change
const state = useStore()
const count = state.count
2

Use primitive selectors when possible

// Good: primitive value comparison
const userName = useStore((state) => state.user.name)

// Less optimal: object comparison
const user = useStore((state) => state.user)
const userName = user.name
3

Use shallow equality for objects

import { useShallow } from 'zustand/react/shallow'

// Prevents re-render if array contents are the same
const todoIds = useStore(
  useShallow((state) => state.todos.map((t) => t.id))
)

Reading Vanilla Store State

When using vanilla stores created with createStore(), use the useStore hook:
import { createStore, useStore } from 'zustand'

const counterStore = createStore<{ count: number }>()((set) => ({
  count: 0,
}))

function Counter() {
  // Use the useStore hook with the vanilla store
  const count = useStore(counterStore, (state) => state.count)
  return <div>Count: {count}</div>
}
See the Vanilla Stores guide for more details on using vanilla stores in React.

Subscribing to Changes

You can subscribe to state changes imperatively:
import { useEffect } from 'react'

function Logger() {
  useEffect(() => {
    const unsubscribe = useBearStore.subscribe((state, prevState) => {
      console.log('Bears changed from', prevState.bears, 'to', state.bears)
    })

    return unsubscribe
  }, [])

  return null
}
The subscribe function returns an unsubscribe function. Always call it in your cleanup to prevent memory leaks.

Next Steps

Updating State

Learn how to update state immutably

Async Actions

Handle asynchronous operations in actions

Build docs developers (and LLMs) love