Home
React
Advanced Hook: useSyncExternalStore()
Daniel Nguyen
Daniel Nguyen
July 19, 2025
1 min

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.

1. Why Do We Need It?

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.

2. The API

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.

3. Example: Geolocation

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 = null
const listeners = new Set<() => void>()
let watchId: number | null = null
function emitChange() {
listeners.forEach(listener => listener())
}
function subscribe(callback: () => void) {
listeners.add(callback)
if (listeners.size === 1 && navigator.geolocation) {
watchId = navigator.geolocation.watchPosition(
position => {
location = position
emitChange()
},
() => {
location = null
emitChange()
},
)
}
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.

4. Example: matchMedia

You 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.


Tags

#ReactAdvancedHooks

Share

Daniel Nguyen

Daniel Nguyen

Frontend Developer

Frontend developer specializing in React, Next.js, and JavaScript. Writing practical guides on modern web development at Dev98.

Expertise

React
Next.js
JavaScript
TypeScript
Python

Social Media

githublinkedinyoutubewebsite

Related Posts

Advanced Hook
Advanced Hook: createPortal()
July 18, 2025
1 min
Dev98

Dev98

React · Next.js · Web development