Other

useSyncExternalStore

Subscribes to an external store and reads a snapshot of its data. The recommended way to integrate non-React state management with React's concurrent features.

Signature

TypeScript
const snapshot = useSyncExternalStore<T>(subscribe: (cb: () => void) => () => void, getSnapshot: () => T, getServerSnapshot?: () => T)

Parameters

ParameterTypeDescription
subscribe(callback: () => void) => () => voidA function that subscribes to the store. Receives a callback to call when the store changes. Returns an unsubscribe function.
getSnapshot() => TA function that returns the current snapshot of the store data. Must return the same value (by Object.is) if the store hasn't changed.
getServerSnapshot() => TOptional function returning the snapshot for server rendering. Required for SSR compatibility.

Return Value

The current snapshot of the store, as returned by getSnapshot. React re-renders the component when the snapshot changes.

Examples

Browser Online Status
import { useSyncExternalStore } from 'react';

function subscribe(callback: () => void) {
  window.addEventListener('online', callback);
  window.addEventListener('offline', callback);
  return () => {
    window.removeEventListener('online', callback);
    window.removeEventListener('offline', callback);
  };
}

function getSnapshot() {
  return navigator.onLine;
}

function getServerSnapshot() {
  return true; // Assume online during SSR
}

function OnlineIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
  return <span>{isOnline ? '🟢 Online' : '🔴 Offline'}</span>;
}
Custom Store
import { useSyncExternalStore } from 'react';

function createStore<T>(initialState: T) {
  let state = initialState;
  const listeners = new Set<() => void>();

  return {
    getSnapshot: () => state,
    subscribe: (listener: () => void) => {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
    setState: (updater: (prev: T) => T) => {
      state = updater(state);
      listeners.forEach(l => l());
    },
  };
}

const counterStore = createStore({ count: 0 });

function Counter() {
  const { count } = useSyncExternalStore(
    counterStore.subscribe,
    counterStore.getSnapshot,
  );

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => counterStore.setState(s => ({ count: s.count + 1 }))}>+</button>
    </div>
  );
}
Window Dimensions Store
import { useSyncExternalStore } from 'react';

let dimensions = { width: 0, height: 0 };

function subscribeDimensions(callback: () => void) {
  const update = () => {
    dimensions = { width: window.innerWidth, height: window.innerHeight };
    callback();
  };
  update();
  window.addEventListener('resize', update);
  return () => window.removeEventListener('resize', update);
}

function getDimensionsSnapshot() {
  return dimensions;
}

function getServerDimensions() {
  return { width: 1024, height: 768 };
}

function useWindowDimensions() {
  return useSyncExternalStore(
    subscribeDimensions,
    getDimensionsSnapshot,
    getServerDimensions,
  );
}

Common Pitfalls

!

Creating new subscribe or getSnapshot functions on every render — define them outside the component or memoize them.

!

Returning a new object from getSnapshot on every call — this causes infinite re-renders. Return the same reference when the data hasn't changed.

!

Not providing getServerSnapshot for SSR, which causes hydration errors.

!

Using useSyncExternalStore for React state — it's designed for external (non-React) stores only.

Understanding useSyncExternalStore

useSyncExternalStore is the official way to subscribe to external data sources in React. It was introduced to solve the "tearing" problem in concurrent rendering, where different parts of the UI could show inconsistent data if they read from an external store at different times during a render. This hook guarantees that all components reading from the same store see a consistent snapshot.

The subscribe function tells React how to listen for store changes. It receives a callback that React calls when it needs to check for updates. The function must return an unsubscribe cleanup function. Crucially, the subscribe function should be stable — either defined outside the component or memoized — to avoid unnecessary resubscriptions on every render.

The getSnapshot function must follow strict rules: it must return the exact same value (by Object.is comparison) if the store hasn't changed. Returning a new object or array on every call, even with identical contents, will cause an infinite re-render loop because React sees each call as returning a "new" value. For stores containing objects, cache the last returned value and only create a new reference when the data actually changes.

This hook is the foundation that state management libraries like Zustand, Jotai, and Valtio use internally to integrate with React's concurrent features. While you can build your own store with useSyncExternalStore, most applications benefit from using an established library that handles edge cases like selector optimization, middleware, and DevTools integration. The hook is most useful when integrating with non-React data sources like browser APIs, legacy stores, or shared workers.

Related Hooks

More Other Hooks

Explore All React Hooks

Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.