useSyncExternalStore is a React hook that lets a component subscribe to a store outside React and re-render when that store changes. It is the bridge for third party state libraries and browser APIs that React does not control. Libraries such as Zustand and Redux use it internally, so most developers consume it through those tools rather than calling it directly.
When you need it
Most components read props, state, and context, and React already knows when those change. useSyncExternalStore is only for data that lives outside React, like a library store or a mutable browser value.
The hook takes three arguments: subscribe, getSnapshot, and an optional getServerSnapshot.
| Argument | Purpose |
|---|---|
| subscribe | Attach a listener and return an unsubscribe function |
| getSnapshot | Return the current value, cached across calls |
| getServerSnapshot | Supply the initial value during server rendering |
The table summarizes the contract. Now build the smallest store and wire it up.
Build the subscribe and getSnapshot functions
getSnapshot reads the current value from the outside store. subscribe attaches the callback React passes in and returns a cleanup function.
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}The online and offline events fire when the connection changes. The returned cleanup removes both listeners so the component never leaks subscriptions after unmounting. React calls subscribe once, passes a callback, and keeps the returned cleanup for when the component unmounts.
Read the value in a component
Call useSyncExternalStore at the top level of a component and pass the two functions. It returns the current snapshot.
import { useSyncExternalStore } from "react";
export function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <p>{isOnline ? "Online" : "Offline"}</p>;
}Disconnect from the network and the component re-renders to show Offline. React re-reads getSnapshot whenever subscribe fires the callback. The hook reads getSnapshot during render and re-reads it whenever the store signals a change, so the component always shows the latest external value.
Extract a custom hook
Most apps wrap useSyncExternalStore in a custom hook instead of calling it directly in every component. The hook hides the subscribe and getSnapshot details.
export function useOnlineStatus() {
return useSyncExternalStore(subscribe, getSnapshot);
}Any component can now call useOnlineStatus to read the same value. This is also how libraries expose their own hooks, since Zustand uses useSyncExternalStore under the hood.
A typical external store
Many external stores already expose subscribe and getSnapshot methods, so the hook call is just passing those methods.
A store that holds a list of todos looks similar to the online example: getSnapshot returns the current list, and subscribe notifies listeners whenever the list changes. Wiring that store is a two line hook call once the methods exist.
Keep getSnapshot cached
getSnapshot must return the same value until the store actually changes. Returning a new object on every call makes React re-render forever.
For immutable stores, return the stored value directly. For mutable stores, cache the last snapshot and return it again when nothing changed.
React compares snapshots with Object.is, so a fresh object every call looks like a change. This is the most common mistake when wiring a custom store, and it shows up as an infinite loop warning in the console.
Support server rendering
On the server there is no navigator or window. Pass getServerSnapshot as the third argument so React has a value while rendering HTML and hydrating.
export function useOnlineStatus() {
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}
function getServerSnapshot() {
return true;
}The server snapshot returns true so the first paint always shows Online. The client then subscribes and corrects the value once the real connection state is known.
Omit getServerSnapshot only when the component never renders on the server. If a server-rendered component omits it, React throws an error during server rendering. A related pattern for browser APIs is in how to synchronize React with browser APIs.
When not to use it
Use built in state first. If the value lives inside React, useState or useReducer is simpler and safer. Reach for useSyncExternalStore only when the data already lives outside React.
Rune AI
Key Insights
- Call useSyncExternalStore with subscribe and getSnapshot.
- subscribe registers a callback and returns cleanup.
- getSnapshot must return a cached or immutable value.
- Add getServerSnapshot when the component renders on a server.
Frequently Asked Questions
Is useSyncExternalStore stable?
Do I need it with Zustand or Redux Toolkit?
What happens if getSnapshot returns a new object every call?
Conclusion
useSyncExternalStore bridges React and stores that live outside React. Provide a subscribe function and a getSnapshot function, keep snapshots cached, and add getServerSnapshot for server rendering.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.