Async actions in Zustand
Zustand actions can be async functions. They call set when they are ready, and can read the current state with get.
How it works
- Write an async action.
loadFishiesis anasyncfunction. It setsstatusto"loading", waits 1.2 seconds as a stand-in for a request, then callssetagain with the list. [1] - Read state inside the action. The store creator receives
getas a second argument.get().loadsreads the current counter so each load is numbered. [1] - Show the status in React. The component selects
statusandfishies, starts a load inuseEffect, and disables the button while loading. [1]
Run and edit it
It runs in your browser, in a sandboxed frame that cannot see this site; the packages load from esm.sh only after you press Run.
import { useEffect } from 'react'
import { create } from 'zustand'
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const useFishStore = create((set, get) => ({
fishies: [],
status: 'idle',
loads: 0,
// An async action: call set whenever the data is ready
loadFishies: async () => {
set({ status: 'loading' })
await wait(1200) // stand-in for a slow request
const loads = get().loads + 1 // read current state with get()
set({ fishies: ['salmon', 'tuna', 'trout'].map((f) => `${f} (load ${loads})`), status: 'done', loads })
},
}))
const colors = { idle: '#9aa0ad', loading: '#f5b84a', done: '#5fd38d' }
export default function App() {
const fishies = useFishStore((state) => state.fishies)
const status = useFishStore((state) => state.status)
const loadFishies = useFishStore((state) => state.loadFishies)
useEffect(() => { loadFishies() }, [loadFishies])
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#111', fontFamily: 'system-ui, sans-serif' }}>
<div style={{ background: '#1c1f26', color: '#e8eaf0', padding: 26, borderRadius: 14, width: 400, boxSizing: 'border-box' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 style={{ margin: 0, fontSize: 20 }}>Fish market</h2>
<span style={{ color: colors[status], fontWeight: 700, fontSize: 13 }}>{status}</span>
</div>
<ul style={{ minHeight: 90, paddingLeft: 20, lineHeight: 1.7 }}>
{status === 'loading' ? <li style={{ color: '#9aa0ad' }}>loading...</li> : fishies.map((f) => <li key={f}>{f}</li>)}
</ul>
<button
onClick={loadFishies}
disabled={status === 'loading'}
style={{ padding: '9px 16px', border: 0, borderRadius: 8, background: '#f5b84a', color: '#1a1a1a', fontWeight: 600, cursor: 'pointer', opacity: status === 'loading' ? 0.5 : 1 }}
>
Reload
</button>
</div>
</div>
)
}
The list loads on its own; press Reload to run the async action again and watch the status change. Code written by AgentGid [1].
The list loads on its own; press Reload to run the async action again and watch the status change. Code written by AgentGid [1].Checked Oct 12, 2026: it starts without errors in Chromium with zustand 5.0.15, react 19.3.0, react-dom 19.3.0. Needs WebGL.
More Zustand examples
Creating a store in Zustand Updating state from inputs in Zustand Using a Zustand store outside components Updating nested state in Zustand
Sources
Explanations written by AgentGid from the official docs; the numbers in brackets point here. The code marked "written by AgentGid" is ours and free to copy.