What is useSyncExternalStore and When Should I Use It in React?
Problem
When I built a React app that needed to track browser online status, I wrote this code:
import { useState, useEffect } from 'react';
function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline);
return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []);
return isOnline;}This worked fine. But then I heard React 18 introduced concurrent rendering, and this pattern could cause “tearing” - where different parts of the UI show different states. Also, my server-side rendering kept failing with hydration mismatches.
I needed a better approach.
Environment
- React 18+
- Node.js 18+
- Next.js (for SSR)
What is useSyncExternalStore?
useSyncExternalStore is a React hook introduced in React 18. It lets you subscribe to external data stores - state managed outside React - while ensuring compatibility with concurrent rendering.
The hook takes three arguments:
subscribe- A function to subscribe to the storegetSnapshot- A function to read the current valuegetServerSnapshot(optional) - A function for server-side rendering
Here’s the basic signature:
const value = useSyncExternalStore( subscribe, // (callback) => unsubscribe getSnapshot, // () => value getServerSnapshot? // () => serverValue);How to Use It
Let me rewrite my online status hook with useSyncExternalStore:
import { useSyncExternalStore } from 'react';
// Subscribe function - stable reference outside componentfunction subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); };}
function useOnlineStatus() { return useSyncExternalStore( subscribe, // Subscribe to events () => navigator.onLine, // Client: get current status () => true // Server: assume online );}
export default useOnlineStatus;Now I can use it in a component:
import useOnlineStatus from './useOnlineStatus';
function ChatIndicator() { const isOnline = useOnlineStatus();
return ( <div className={isOnline ? 'online' : 'offline'}> {isOnline ? 'Connected' : 'Disconnected'} </div> );}This is cleaner than my useEffect version. The subscription logic is separate from the component, and React handles the cleanup automatically.
When to Use It
I use useSyncExternalStore when connecting React to:
Browser APIs:
- Online/offline status
- Window dimensions
- localStorage changes
- Page visibility
External state stores:
- Third-party state managers (Redux, Zustand)
- Custom stores outside React
Libraries:
- Data fetching libraries
- WebSocket connections
Here’s another example with window size:
import { useSyncExternalStore } from 'react';
function useWindowSize() { return useSyncExternalStore( // Subscribe to resize events (callback) => { window.addEventListener('resize', callback); return () => window.removeEventListener('resize', callback); }, // Client snapshot () => ({ width: window.innerWidth, height: window.innerHeight }), // Server snapshot - static values for SSR () => ({ width: 1024, height: 768 }) );}
export default useWindowSize;When NOT to Use It
I still use useEffect for:
- Synchronizing with React state (not external stores)
- Running side effects that don’t produce render values
- Component lifecycle events
useSyncExternalStore is specifically for external stores that need to trigger re-renders.
A Common Mistake
I initially made this mistake - defining the subscribe function inside the component:
function useOnlineStatus() { // WRONG: subscribe created on every render const subscribe = (callback) => { window.addEventListener('online', callback); return () => window.removeEventListener('online', callback); };
return useSyncExternalStore( subscribe, () => navigator.onLine );}This causes unnecessary resubscriptions. The subscribe function should have a stable reference, defined outside the component or memoized.
Migration from useEffect
Here’s a side-by-side comparison. The old pattern:
import { useState, useEffect } from 'react';
function useLocalStorage(key, defaultValue) { const [value, setValue] = useState(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : defaultValue; });
useEffect(() => { const handleStorageChange = (e) => { if (e.key === key) { setValue(JSON.parse(e.newValue)); } };
window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, [key]);
return value;}The new pattern:
import { useSyncExternalStore } from 'react';
function useLocalStorage(key, defaultValue) { const subscribe = (callback) => { const handleStorageChange = (e) => { if (e.key === key) callback(); }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); };
const getSnapshot = () => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : defaultValue; };
const getServerSnapshot = () => defaultValue;
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);}The new version is more declarative. React handles the subscription lifecycle, and the server snapshot prevents hydration mismatches.
The Reason
The key difference between useEffect and useSyncExternalStore:
useEffect problems:
- Not designed for external store synchronization
- Can cause tearing with concurrent rendering
- Manual cleanup is error-prone
- SSR requires extra handling
useSyncExternalStore benefits:
- Built for external stores
- Concurrent-safe by design
- Automatic subscription management
- SSR support built-in
React team recommends useSyncExternalStore for any library integrating with external state. Modern data fetching libraries like React Query and SWR already use it internally.
Summary
In this post, I showed what useSyncExternalStore is and when to use it. The key point is: use it when connecting React to external data sources like browser APIs or third-party state stores. It replaces error-prone useEffect patterns with a declarative API that handles subscriptions, cleanup, and SSR automatically.
If you’re using React Query or similar libraries, you’re already benefiting from useSyncExternalStore without knowing it.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments