Hooks
useOnline
An SSR-safe hook that subscribes to the browser's online/offline events and returns whether the device currently has a network connection.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/use-online.jsonPrompt
Build a React + TypeScript "useOnline" hook (no dependencies beyond React;
uses the browser's `navigator.onLine` and `window` online/offline events).
Contract
- `useOnline(options?: { serverFallback?: boolean }): boolean`.
- `serverFallback` defaults to `true` — most page loads happen on a live
connection, so assuming online avoids flashing a false "offline" state on
first paint for the common case.
Behavior
- Built on `useSyncExternalStore`, not `useEffect` + `useState`:
- `subscribe(callback)`: register `callback` on both `window`'s `"online"`
and `"offline"` events, return a cleanup that unregisters both. Guard for
`typeof window === "undefined"` and return a no-op subscribe in that case.
- `getSnapshot()`: return `navigator.onLine`.
- `getServerSnapshot()`: return `serverFallback`.
- Zero `useEffect` + `setState`: online/offline is an external, mutable
source that changes outside React's render cycle, which is exactly the
case `useSyncExternalStore` exists for.
- Honest boundary the hook must document and never paper over:
`navigator.onLine` only reflects whether the device has *a* network
interface (Wi-Fi/Ethernet/cellular) attached — it does not confirm that
interface can actually reach your server. A device connected to a Wi-Fi
network whose upstream internet is down is still commonly reported as
online by the browser. This is a semantic limit of the browser API, not a
bug in the hook; reachability monitoring (a periodic health-check `fetch`)
is a separate, explicit concern layered on top by the consumer.
Rendering & styling
- The hook renders nothing itself — it returns a boolean. Consumers branch
on it directly (disable a button, swap in a banner) using semantic tokens
for anything visual (`bg-destructive/10 text-destructive` for an offline
state, `var(--chart-2)` for an online indicator dot); there's no styling
contract to enforce inside the hook itself.
Customization levers
- `serverFallback` — flip the SSR guess to `false` if your audience is more
often offline on first load than online (e.g. a PWA aimed at spotty
connectivity), to reduce the hydration flash in the other direction.
- Reachability heartbeat — if you need "can I actually reach my API", layer
a `setInterval` `fetch("/api/health")` on top of (not inside) this hook and
combine the two booleans; this hook only ever answers "does the device
have a network interface".
- Pairing with a global banner component — render your app's toast/banner
primitive keyed off this hook's return value at a layout level, instead of
duplicating the offline-banner JSX in every feature that needs it.Concepts
useSyncExternalStoreas the online/offline adapter — same shape as subscribing tomatchMedia: hand React the three primitives an external store needs (subscribe, snapshot, server snapshot) and let it decide when to re-render, tear-safe under concurrent rendering.- Interface presence vs. end-to-end reachability —
navigator.onLineanswers "is a network interface up", not "can I reach my backend". A laptop on a Wi-Fi network whose router lost its upstream internet still reportstrue. Treat this hook's output as a fast, free, local-only signal — pair it with an active health-checkfetchwhen you actually need reachability. - Two events, one boolean — the browser fires
"online"and"offline"onwindowwhenever the OS-reported connectivity state flips; both events feed the samecallback, andgetSnapshotre-reads the current truth (navigator.onLine) rather than trusting which event fired, so out-of-order or coalesced events can't desync the returned value. serverFallbackvs. hydration mismatch — the server has no network interface of its own to query, so it renders a guess; defaulting totruematches the common case (most visits are online) and keeps the first client render consistent with the server-rendered markup, avoiding a hydration warning.- State-driven degraded UI — the representative use of this hook isn't the status badge itself, it's branching real UI on the boolean: disabling a submit button and surfacing a banner while offline is the same pattern as any other server-state-driven conditional render, just fed by a browser event instead of a fetch.