React useSyncExternalStore — Subscribing to Browser APIs Safely
Learn how to use useSyncExternalStore to safely subscribe to browser APIs and external state stores in concurrent React. Covers online status, media queries, and Redux integration.
Detailed Explanation
External Store Subscription with useSyncExternalStore
useSyncExternalStore provides a safe way to subscribe to external data sources in concurrent React, preventing tearing (showing inconsistent data from different points in time).
Online Status Hook
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
},
() => navigator.onLine,
() => true // Server snapshot
);
}
Media Query Hook
function useMediaQuery(query: string) {
return useSyncExternalStore(
(callback) => {
const mql = window.matchMedia(query);
mql.addEventListener("change", callback);
return () => mql.removeEventListener("change", callback);
},
() => window.matchMedia(query).matches,
() => false
);
}
// Usage
const isDark = useMediaQuery("(prefers-color-scheme: dark)");
const isMobile = useMediaQuery("(max-width: 768px)");
The Three Arguments
- subscribe: Takes a callback and returns an unsubscribe function
- getSnapshot: Returns the current value (must return the same reference if unchanged)
- getServerSnapshot: Returns the value for SSR (required for server-rendered components)
Common Mistake: New Object Every Call
// BUG: Returns a new object every time, causing infinite re-renders
() => ({ width: window.innerWidth, height: window.innerHeight })
// FIX: Return a primitive, or cache the object
() => window.innerWidth // Primitive -- safe
When to Use
Use useSyncExternalStore for external state that is not managed by React: browser APIs, third-party state management libraries, or shared mutable data sources. For React-managed state, use useState or useReducer instead.
Use Case
Use useSyncExternalStore for subscribing to browser APIs (online status, media queries, geolocation), integrating external state management libraries (Redux, Zustand) with concurrent React, and reading from shared mutable data sources.