usePolling
Timing & asyncPoll an async function on an interval — non-overlapping ticks, pause/resume, an enabled gate, and exponential backoff.
Install
$ npm install @usefy/use-pollingQuick start
import { usePolling } from "@usefy/use-polling";
function LiveStatus() {
const { data, status, isPolling, pause, resume } = usePolling(
async (signal: AbortSignal) => {
const res = await fetch("/api/status", { signal });
return (await res.json()) as { online: number };
},
{ interval: 5000, immediate: true, backoff: { factor: 2, maxInterval: 30_000 } },
);
return (
<div>
<p>{data ? `${data.online} online` : "…"} ({status})</p>
<button onClick={isPolling ? pause : resume}>
{isPolling ? "Pause" : "Resume"}
</button>
</div>
);
}API reference
const {
data, error, status, isLoading,
isPolling, pause, resume, start, stop,
} = usePolling<T, Args, E>(fn, options?);
Parameters
| Parameter | Type | Description |
|---|---|---|
fn | (signal: AbortSignal, ...args: Args) => Promise<T> | The async function to poll. Receives an AbortSignal first, then options.args. Wire the signal into fetch(url, { signal }). Read through a ref — an inline function is fine and never goes stale. |
options | UsePollingOptions<T, Args, E> | Optional. See below. |
Options — UsePollingOptions<T, Args, E>
| Option | Type | Default | Description |
|---|---|---|---|
interval | number | 1000 | Base delay (ms) between one poll settling and the next starting. Applies from the next tick — never restarts the loop. |
immediate | boolean | true | Poll immediately when the loop starts/resumes, vs. waiting one interval. |
enabled | boolean | true | Declarative master gate. false → never polls; flipping to true (re)starts the loop. While false, resume() cannot start polling. |
backoff | boolean | { factor?, maxInterval? } | (failures, base) => number | false | Grow the delay on consecutive failures, reset on success. true → exponential factor: 2; object → tuned exponential (factor default 2, maxInterval default Infinity); function → full control. |
args | Args | [] | Arguments forwarded to fn (after the signal) each poll. Read fresh per tick. |
initialData | T | — | Seed for data before the first success. Status still starts "idle". |
onSuccess | (data: T) => void | — | Called after a successful poll (non-superseded, while polling). Fired from the event turn, never inside a state updater. |
onError | (error: E) => void | — | Called after a failed poll. The abort of a paused/stopped/unmounted poll is never reported here. |
Return — UsePollingReturn<T, E>
| Field | Type | Description |
|---|---|---|
data | T | undefined | The most recent successfully-resolved value. Retained across later pending/error transitions. |
error | E | undefined | The error from the most recent failed poll. Cleared when a poll starts and on success. |
status | "idle" | "pending" | "success" | "error" | The lifecycle status of the latest poll — the source of truth. |
isLoading | boolean | Convenience mirror of status === "pending". |
isPolling | boolean | Whether the loop is active — i.e. enabled && !paused. |
pause / stop | () => void | Halt the loop: no new polls, clear the pending timeout, abort the in-flight poll. Stable. (stop is an alias of pause.) |
resume / start | () => void | Restart the loop (respecting immediate). No effect while enabled is false. Stable. (start is an alias of resume.) |
Behavioural guarantees (by design)
- No overlapping polls: the next tick is scheduled with
setTimeoutafter the current poll settles, so a slowfnnever stacks in-flight requests (unlike a naivesetInterval). - Controls vs. gate precedence:
enabledis the declarative master switch;pause/resumeare the imperative override within an enabled session.isPolling === enabled && !paused. Whileenabledisfalse,resume()is inert. - Pausing a pending poll:
pause/stop/enabled:false/unmount abort the in-flight poll via itsAbortSignaland discard its result. If a poll was in flight,statussettles back to its last resolved value (successif there was data, elseidle) andisLoadingclears — it never sticks onpending. - Backoff: the delay grows only on consecutive failures (
baseInterval * factor ** failureCount, clamped tomaxInterval) and resets tointervalthe moment a poll succeeds. - What restarts the loop: only
enabledandpause/resumestate. A changedinterval,args,backoff, callbacks, or inlinefnare read through refs and apply on the next tick without tearing the loop down. - StrictMode: the double-invoked mount effect tears its first loop down (clearing the timeout, aborting in-flight) before starting the second, so exactly one self-scheduling loop is ever live.
Exported types
UsePollingOptions<T, Args, E>, UsePollingReturn<T, E>, PollingBackoff, BackoffOptions, BackoffFn, plus the pure helper computePollingDelay(failureCount, baseInterval, backoff?), the DEFAULT_POLLING_INTERVAL constant, and the shared AsyncStatus, AsyncState<T, E>, AsyncFn<T, Args>, AsyncFnWithSignal<T, Args> re-exported from the async siblings.
Go deeper
This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.