This guide is only relevant if you’re using React versions before React 18 . React 18 and later handle batching automatically.
The Problem
In React versions before 18, setState is handled synchronously when called outside a React event handler. This means updating Zustand state outside an event handler will force React to update components synchronously, which can lead to the zombie-child effect .
What is the Zombie-Child Effect?
The zombie-child effect occurs when:
A parent component re-renders
A child component reads stale data before it can re-render
The child makes decisions based on outdated state
The Solution: unstable_batchedUpdates
Wrap your state updates in unstable_batchedUpdates to batch updates and prevent this issue:
import { unstable_batchedUpdates } from 'react-dom' // or 'react-native'
const useFishStore = create (( set ) => ({
fishes: 0 ,
increaseFishes : () => set (( prev ) => ({ fishes: prev . fishes + 1 })),
}))
const nonReactCallback = () => {
unstable_batchedUpdates (() => {
useFishStore . getState (). increaseFishes ()
})
}
Common Scenarios
setTimeout
WebSocket
Fetch Callback
Third-party Library
import { unstable_batchedUpdates } from 'react-dom'
// Outside React event handler
setTimeout (() => {
unstable_batchedUpdates (() => {
useFishStore . getState (). increaseFishes ()
useBearStore . getState (). increaseBears ()
})
}, 1000 )
import { unstable_batchedUpdates } from 'react-dom'
const socket = new WebSocket ( 'ws://localhost:8080' )
socket . addEventListener ( 'message' , ( event ) => {
const data = JSON . parse ( event . data )
unstable_batchedUpdates (() => {
useMessageStore . getState (). addMessage ( data )
useNotificationStore . getState (). incrementUnread ()
})
})
import { unstable_batchedUpdates } from 'react-dom'
fetch ( '/api/data' )
. then ( response => response . json ())
. then ( data => {
unstable_batchedUpdates (() => {
useDataStore . getState (). setData ( data )
useLoadingStore . getState (). setLoading ( false )
})
})
import { unstable_batchedUpdates } from 'react-dom'
import someLibrary from 'some-library'
someLibrary . onEvent (( data ) => {
// Library callback - not a React event handler
unstable_batchedUpdates (() => {
useStore . getState (). updateFromLibrary ( data )
})
})
When Do You Need This?
Need unstable_batchedUpdates
setTimeout/setInterval callbacks
WebSocket message handlers
Fetch/Promise callbacks
Third-party library callbacks
Browser event listeners (addEventListener)
Don't Need It
onClick, onChange, etc. (React synthetic events)
useEffect hooks
React 18+ applications (automatic batching)
Synchronous updates in render
React Native
For React Native applications, import from react-native instead:
import { unstable_batchedUpdates } from 'react-native'
const nonReactCallback = () => {
unstable_batchedUpdates (() => {
useFishStore . getState (). increaseFishes ()
})
}
Upgrading to React 18
If you upgrade to React 18 or later, you can remove all unstable_batchedUpdates calls. React 18 automatically batches all state updates, regardless of where they occur.
Migration Example
React 17 and Earlier
React 18+
import { unstable_batchedUpdates } from 'react-dom'
setTimeout (() => {
unstable_batchedUpdates (() => {
useStore . getState (). updateValue ()
})
}, 1000 )
Additional Resources
GitHub Issue #302 Read more details about this issue and the discussion around batched updates in Zustand
Consider upgrading to React 18 or later to benefit from automatic batching and avoid this complexity altogether.