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.
Recommended: Colocated Actions
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
Colocated Actions
External Actions
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.
import { useBoundStore , inc } from './store'
function Counter () {
const count = useBoundStore (( state ) => state . count )
// No need to select the action from the store
return (
< div >
< p > Count: { count } </ p >
< button onClick = { inc } > Increment </ button >
</ div >
)
}
Actions can be imported and called directly without hooks.
Using Actions Outside Components
External actions shine when called outside of React components:
Event Handler
Utility Function
Async Operation
import { inc , setText } from './store'
// Call directly in event listeners
document . getElementById ( 'btn' ). addEventListener ( 'click' , () => {
inc ()
setText ( 'clicked' )
})
Complete Example
Define Store with External Actions
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 ),
}))
Use in Components
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 >
)
}
Use Outside Components
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