Subscribing to an external store with useEffect and useState tears under concurrent rendering, which is what this hook exists to prevent.
const online = useSyncExternalStore(
(cb) => {
window.addEventListener('online', cb)
window.addEventListener('offline', cb)
return () => {
window.removeEventListener('online', cb)
window.removeEventListener('offline', cb)
}
},
() => navigator.onLine,
() => true, // the server snapshot
)
The snapshot function must return a cached value rather than a fresh object, or the comparison is never equal and the component renders forever — that is the mistake everybody makes once. The third argument matters only for server rendering and returning something sensible there is easier than discovering the hydration mismatch later.