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.