Why Use useSyncExternalStore in React
React 18 introduced the useSyncExternalStore hook to
solve the problem of “tearing” when subscribing to external data stores
during concurrent rendering. This article covers what this hook does,
why traditional state synchronization methods fail in modern React, and
how adopting this hook ensures UI consistency, simplifies subscription
logic, and provides native support for server-side rendering.
The Problem of Tearing in Concurrent React
Before React 18, rendering was synchronous and uninterruptible. If an
external data store (like Redux, Zustand, or a browser API like
window.navigator.onLine) changed during a render, it did
not cause visual inconsistencies because the render completed in a
single block of time.
With the introduction of Concurrent Rendering, React can pause, resume, or abandon renders to keep the main thread responsive. If an external store updates while a render is paused, components that render before the pause might show the old data, while components that render after the pause will show the new data. This visual inconsistency is known as “tearing.”
Why Traditional useEffect Subscriptions Fail
Historically, developers subscribed to external stores using a
combination of useState and useEffect:
// The legacy, error-prone approach
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});
return unsubscribe;
}, []);This pattern is problematic in modern React for several reasons: 1. No Concurrent Safety: It does not prevent tearing. React has no way of knowing that the state update originated from an external store, meaning it cannot coordinate the UI updates safely. 2. Boilerplate: Developers must manually write setup, cleanup, and synchronization logic for every subscription. 3. Hydration Mismatches: During Server-Side Rendering (SSR), the server state and client state can easily drift, leading to hydration errors.
How useSyncExternalStore Solves These Issues
The useSyncExternalStore hook is specifically designed
to read and subscribe to stores external to React in a way that is
compatible with concurrent rendering.
1. Guarantees UI Consistency (No Tearing)
The hook automatically detects when an external store has changed during a render. If a change occurs, React discards the current render and restarts it synchronously, ensuring that every component on the screen reflects the exact same snapshot of the external data.
2. Built-in Server-Side Rendering (SSR) Support
The hook accepts three arguments: * subscribe: A
function to register a callback that React calls whenever the store
changes. * getSnapshot: A function that returns the current
value of the store. * getServerSnapshot (optional): A
function that returns the snapshot used during server rendering and
hydration.
By using getServerSnapshot, you prevent hydration
mismatch warnings by ensuring the server-rendered HTML matches the
initial client-side render, even if the client-side store has access to
browser-only APIs.
3. A Clean, Standardized API
Implementing the hook is straightforward. Here is how you can
subscribe to a browser API, such as the user’s online status, using
useSyncExternalStore:
import { useSyncExternalStore } from 'react';
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
export function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
getSnapshot,
() => true // Server snapshot fallback
);
}This approach replaces verbose useEffect setups with a
highly performant, standardized subscription mechanism that React
optimizes internally.