patternjavascriptreactModerate
useSyncExternalStore for safe external store subscriptions
Viewed 0 times
React 18+ (native); use-sync-external-store shim for older versions
useSyncExternalStoretearingconcurrent modeexternal storesubscriptionzustand
Error Messages
Problem
Reading from external stores (Redux, Zustand internal, window dimensions, localStorage) inside a useEffect or directly in render can cause tearing in React concurrent mode — different components render with different snapshots of the store during the same update.
Solution
Use useSyncExternalStore to safely subscribe to any external source:
import { useSyncExternalStore } from 'react';
// Subscribe to window online status
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine, // getSnapshot for browser
() => true // getServerSnapshot for SSR
);
}
// Subscribe to a custom store
const store = createMyStore();
function useStore(selector) {
return useSyncExternalStore(
store.subscribe,
() => selector(store.getState()),
() => selector(store.getInitialState())
);
}
import { useSyncExternalStore } from 'react';
// Subscribe to window online status
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine, // getSnapshot for browser
() => true // getServerSnapshot for SSR
);
}
// Subscribe to a custom store
const store = createMyStore();
function useStore(selector) {
return useSyncExternalStore(
store.subscribe,
() => selector(store.getState()),
() => selector(store.getInitialState())
);
}
Why
In concurrent mode, React can interrupt and restart renders. If you read from an external source directly (not via React state), different components in the same tree may see different versions of that source, causing visual inconsistencies called tearing. useSyncExternalStore eliminates tearing by synchronizing the snapshot for the entire render.
Gotchas
- getSnapshot must return a cached/stable value — creating new objects every call causes infinite re-renders
- The third argument (getServerSnapshot) is required if your component renders on the server
- Available in React 18+ natively; use the 'use-sync-external-store' shim for React 16/17
- State management libraries like Zustand and Jotai already use this internally
Code Snippets
Window width with useSyncExternalStore
function useWindowWidth() {
return useSyncExternalStore(
(cb) => {
window.addEventListener('resize', cb);
return () => window.removeEventListener('resize', cb);
},
() => window.innerWidth,
() => 1024 // SSR fallback
);
}Context
When subscribing to external data sources (browser APIs, custom stores) in React 18+ with concurrent features
Revisions (0)
No revisions yet.