useSyncExternalStore is a React Hook introduced in React 18 that lets components subscribe to external stores and read their current state safely during concurrent rendering.
It is useful when the source of truth lives outside React, such as browser APIs, Redux stores, or custom event-based stores.
A common approach before React 18 was to combine useEffect and useState:
useEffect(() => {return store.subscribe(() => {setValue(store.getValue())})}, [])
This can work, but it requires manual synchronization and may miss changes between rendering and subscription setup.
useSyncExternalStore provides a standard subscription mechanism that helps React avoid inconsistent snapshots, also known as tearing.
const snapshot = useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot, // optional)
subscribe: Registers a callback and returns an unsubscribe function.getSnapshot: Reads the current snapshot of the external store.getServerSnapshot: Supplies the snapshot used during server rendering and hydration.React checks the snapshot and schedules updates when the external store notifies subscribers.
Geolocation is an external browser API. A small store can keep the latest position and notify React when it changes.
import { useSyncExternalStore } from 'react'let location: GeolocationPosition | null = nullconst listeners = new Set<() => void>()let watchId: number | null = nullfunction emitChange() {listeners.forEach(listener => listener())}function subscribe(callback: () => void) {listeners.add(callback)if (listeners.size === 1 && navigator.geolocation) {watchId = navigator.geolocation.watchPosition(position => {location = positionemitChange()},() => {location = nullemitChange()},)}return () => {listeners.delete(callback)if (listeners.size === 0 && watchId !== null) {navigator.geolocation.clearWatch(watchId)watchId = null}}}function getSnapshot() {return location}function getServerSnapshot() {return null}function MyLocation() {const position = useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot,)if (!position) return <p>Location unavailable</p>return (<p>{position.coords.latitude}, {position.coords.longitude}</p>)}
This example shares one subscription across consumers and cleans it up when no consumers remain. A production implementation should also expose loading and permission-denied states separately.
Important: The browser requires permission to access geolocation. The server snapshot is null because location is unavailable during server rendering.
matchMediaYou can use useSyncExternalStore to subscribe to browser media-query changes.
import {useCallback,useMemo,useSyncExternalStore,} from 'react'function useMediaQuery(query: string) {const mediaQuery = useMemo(() =>typeof window !== 'undefined'? window.matchMedia(query): null,[query],)const subscribe = useCallback((callback: () => void) => {if (!mediaQuery) return () => {}mediaQuery.addEventListener('change', callback)return () =>mediaQuery.removeEventListener('change', callback)},[mediaQuery],)const getSnapshot = useCallback(() => mediaQuery?.matches ?? false,[mediaQuery],)const getServerSnapshot = useCallback(() => false, [])return useSyncExternalStore(subscribe,getSnapshot,getServerSnapshot,)}function App() {const isNarrow = useMediaQuery('(max-width: 600px)')return <div>{isNarrow ? 'Narrow' : 'Wide'}</div>}
The media query is created only in the browser. React subscribes to its change event and reads the current matches value.
The server snapshot returns false as a fallback. If the actual browser value differs, React updates after hydration. For SSR applications, consider whether CSS media queries would be a better solution for purely visual responsiveness.