useSyncExternalStore

useSyncExternalStore is a React Hook for subscribing to data that lives outside React — a browser API, a global object, or a state library — in a way that stays consistent during concurrent rendering and avoids "tearing." You give it a subscribe function and a getSnapshot function, and React keeps your component in sync.

Learn useSyncExternalStore in our free React course — an interactive lesson with runnable examples, a practice exercise and a quick recall.

Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1️⃣ The Two Required Functions

The hook needs a subscribe(callback) that registers a listener and returns a cleanup/unsubscribe function, plus a getSnapshot() that synchronously returns the current value. When the store changes it calls the listeners; React then calls getSnapshot and re-renders if the value differs.

2️⃣ Tearing — Why This Hook Exists

In concurrent rendering React can pause mid-render. If two components read external state with plain useEffect , one might see the old value and one the new — the screen is "torn." Because every component reads through the same getSnapshot , this hook guarantees a single, consistent value for the whole render.

3️⃣ Build Your Own Subscribe + Snapshot

The pattern is small once you've seen it: keep a set of listeners, subscribe adds one and returns a remover, getSnapshot returns the current value, and your setters notify all listeners. One caution: getSnapshot must return a stable reference — return primitives or a cached object, never a freshly built one each call, or you'll loop.

These lines build an online-status hook. Put them in the right order:

📋 Quick Reference

An unsubscribe (cleanup) function, so React can stop listening when the component unmounts.

2. Why must getSnapshot return a stable reference?

React compares snapshots to decide whether to re-render. A new object every call looks like a change every time and causes an infinite loop.

Different parts of the UI reading different values of the same store in one render. A single shared getSnapshot prevents it.

Build the smallest possible external store — getSnapshot, subscribe, and an increment that notifies listeners. Run it and check your output.

Practice quiz

What is useSyncExternalStore designed for?

  • Managing ordinary component state
  • Replacing useMemo for expensive calculations
  • Subscribing to data that lives outside React in a tearing-safe way
  • Fetching data from a REST API

Answer: Subscribing to data that lives outside React in a tearing-safe way. useSyncExternalStore subscribes to an external store (browser API, global object, state library) and keeps reads consistent during concurrent rendering.

What are the two required arguments to useSyncExternalStore?

  • subscribe and getSnapshot
  • initialValue and reducer
  • ref and callback
  • deps and effect

Answer: subscribe and getSnapshot. You pass subscribe(callback) and getSnapshot(). A third optional getServerSnapshot is used for SSR.

What must the subscribe function return?

  • The current value of the store
  • A boolean indicating success
  • A promise that resolves when subscribed
  • An unsubscribe (cleanup) function

Answer: An unsubscribe (cleanup) function. subscribe registers a listener and must return a function that removes it, so React can unsubscribe when the component unmounts.

What does getSnapshot do?

  • Asynchronously fetches the next value
  • Synchronously returns the store's current value
  • Schedules a re-render after a delay
  • Serializes the store to JSON

Answer: Synchronously returns the store's current value. getSnapshot returns the current value synchronously. React calls it to read the store and compares results to decide whether to re-render.

Why must getSnapshot return a stable reference?

  • React compares snapshots; a new object every call looks like a change and causes an infinite loop
  • To keep the store small in memory
  • Because getSnapshot must be async
  • So it can be cached on the server

Answer: React compares snapshots; a new object every call looks like a change and causes an infinite loop. React re-renders when the snapshot differs. Returning a freshly built object each call always looks 'changed', triggering an infinite re-render loop. Return primitives or a cached reference.

What is 'tearing'?

  • When a component throws during render
  • When a network request fails
  • When different parts of the UI read different values of the same store in one render
  • When state updates are batched together

Answer: When different parts of the UI read different values of the same store in one render. Tearing is inconsistency where one part of the screen shows the old value and another the new. A shared getSnapshot gives everyone the same value per render.

What is the optional third argument getServerSnapshot for?

  • Caching network responses
  • Providing the value used during server rendering so hydration matches
  • Throttling store updates
  • Logging store changes to the console

Answer: Providing the value used during server rendering so hydration matches. getServerSnapshot supplies an SSR-safe value (browser APIs like navigator don't exist on the server), keeping client hydration consistent.

Which is a good use case for writing useSyncExternalStore directly?

  • Storing a form's input value
  • Memoizing a derived list
  • Passing props down a few levels
  • Subscribing to browser online/offline status or window size

Answer: Subscribing to browser online/offline status or window size. It shines for genuinely external sources like navigator.onLine, window size, or media queries. Ordinary app state should use useState/context or a library.

How do popular state libraries like Redux and Zustand relate to this hook?

  • They forbid its use
  • They call useSyncExternalStore internally to subscribe React components safely
  • It replaces them entirely for all app state
  • They only work in class components instead

Answer: They call useSyncExternalStore internally to subscribe React components safely. Modern store libraries call useSyncExternalStore under the hood, which is why their hooks integrate cleanly with concurrent React.

If subscribe fails to return a cleanup function, what happens?

  • Nothing — it is optional
  • The component refuses to mount
  • React can't unsubscribe and you leak listeners
  • getSnapshot stops being called

Answer: React can't unsubscribe and you leak listeners. Without a returned cleanup, React cannot remove the listener on unmount, leaking subscriptions. Always return () => removeListener().