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…

"use client"

import * as React from "react"

export interface UseOnlineOptions {
  /** Snapshot used during SSR and before hydration. Default `true` — most visits happen
   *  online, and assuming online flashes a false "offline" banner far less often. */
  serverFallback?: boolean
}

/**
 * Subscribe to the browser's network status and return whether it is currently online.
 * `useSyncExternalStore` wires `window`'s `online`/`offline` events into the React tree as an
 * external source: subscribe attaches both listeners, getSnapshot reads `navigator.onLine`, and

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/use-online.json

Prompt

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

  • useSyncExternalStore as the online/offline adapter — same shape as subscribing to matchMedia: 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 reachabilitynavigator.onLine answers "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 reports true. Treat this hook's output as a fast, free, local-only signal — pair it with an active health-check fetch when you actually need reachability.
  • Two events, one boolean — the browser fires "online" and "offline" on window whenever the OS-reported connectivity state flips; both events feed the same callback, and getSnapshot re-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.
  • serverFallback vs. hydration mismatch — the server has no network interface of its own to query, so it renders a guess; defaulting to true matches 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.

On This Page