Skip to main content
The recommended usage is to colocate actions and state within the store. However, you can also define actions externally at the module level for certain advantages. The standard approach keeps actions and state together:
export const useBoundStore = create((set) => ({
  count: 0,
  text: 'hello',
  inc: () => set((state) => ({ count: state.count + 1 })),
  setText: (text) => set({ text }),
}))
This creates a self-contained store with data and actions together, which is the recommended pattern for most use cases.

Alternative: External Actions

Actions can be defined at the module level, outside the store:
export const useBoundStore = create(() => ({
  count: 0,
  text: 'hello',
}))

export const inc = () =>
  useBoundStore.setState((state) => ({ count: state.count + 1 }))

export const setText = (text) => useBoundStore.setState({ text })

Advantages of External Actions

No Hook Required

Call actions without using a hook—useful in event handlers, utilities, or non-React code

Code Splitting

Facilitates code splitting by separating action logic from state definition

Usage Comparison

import { useBoundStore } from './store'

function Counter() {
  const count = useBoundStore((state) => state.count)
  const inc = useBoundStore((state) => state.inc)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={inc}>Increment</button>
    </div>
  )
}
Must use hooks to access both state and actions.

Using Actions Outside Components

External actions shine when called outside of React components:
import { inc, setText } from './store'

// Call directly in event listeners
document.getElementById('btn').addEventListener('click', () => {
  inc()
  setText('clicked')
})

Complete Example

1

Define Store with External Actions

store.js
import { create } from 'zustand'

// Store only contains state
export const useStore = create(() => ({
  count: 0,
  text: 'hello',
  todos: [],
}))

// Actions are exported separately
export const inc = () =>
  useStore.setState((state) => ({ count: state.count + 1 }))

export const dec = () =>
  useStore.setState((state) => ({ count: state.count - 1 }))

export const setText = (text) => useStore.setState({ text })

export const addTodo = (todo) =>
  useStore.setState((state) => ({ todos: [...state.todos, todo] }))

export const removeTodo = (index) =>
  useStore.setState((state) => ({
    todos: state.todos.filter((_, i) => i !== index),
  }))
2

Use in Components

App.jsx
import { useStore, inc, dec, setText, addTodo } from './store'

function App() {
  const count = useStore((state) => state.count)
  const text = useStore((state) => state.text)
  const todos = useStore((state) => state.todos)

  return (
    <div>
      <div>
        <p>Count: {count}</p>
        <button onClick={inc}>+</button>
        <button onClick={dec}>-</button>
      </div>

      <div>
        <input
          value={text}
          onChange={(e) => setText(e.target.value)}
        />
      </div>

      <div>
        <button onClick={() => addTodo(text)}>Add Todo</button>
        <ul>
          {todos.map((todo, i) => (
            <li key={i}>{todo}</li>
          ))}
        </ul>
      </div>
    </div>
  )
}
3

Use Outside Components

analytics.js
import { inc, setText } from './store'

// Track button clicks
export function trackClick() {
  analytics.track('button_clicked')
  inc()
}

// Handle global keyboard shortcuts
window.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    setText('')
  }
})

When to Use This Pattern

  • Calling actions from non-React code (event listeners, utilities)
  • Large applications with code-splitting requirements
  • Actions that need to be called before React tree is mounted
  • Sharing actions across multiple components without prop drilling
  • Testing actions in isolation
  • Small to medium applications where encapsulation is preferred
  • When all actions are only called from React components
  • When you want a single source of truth with clear boundaries
  • When using TypeScript and want automatic type inference for actions

Combining Both Patterns

You can mix both approaches:
export const useStore = create((set) => ({
  count: 0,
  text: 'hello',
  // Some actions colocated
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

// Some actions external
export const setText = (text) => useStore.setState({ text })

TypeScript Support

import { create } from 'zustand'

interface State {
  count: number
  text: string
}

export const useStore = create<State>()(() => ({
  count: 0,
  text: 'hello',
}))

export const inc = () =>
  useStore.setState((state) => ({ count: state.count + 1 }))

export const setText = (text: string) => useStore.setState({ text })

Trade-offs

Colocated Actions

Pros: Encapsulation, single source of truth, better type inferenceCons: Must use hooks, harder to call from outside React

External Actions

Pros: No hooks needed, easier code splitting, flexible usageCons: Less encapsulation, actions and state are separate
While this pattern doesn’t offer any downsides, some developers prefer colocating actions due to its encapsulated nature and clearer boundaries.

Flux-Inspired Practices

Learn recommended patterns for structuring stores

Auto-generating Selectors

Generate selectors automatically for better ergonomics

Build docs developers (and LLMs) love