usePolling

All about usePolling hook in React


A reusable polling hook to run an async function at a specified interval.

LinkIconUsage

import React from "react";
import { usePolling } from "./usePolling";
 
async function fetchServerStatus() {
  const res = await fetch("/api/status");
  if (!res.ok) throw new Error("Failed to fetch status");
  return res.json();
}
 
export function ServerStatusChecker() {
  const { 
    data, 
    error, 
    isRunning, 
    start, 
    stop 
  } = usePolling(fetchServerStatus, {
    interval: 30 * 60 * 1000, // 30 minutes
    immediate: true,
    autoStart: true,
  });
 
  return (
    <div>
      <h2>Server Status</h2>
      {error && <p>Error: {String(error)}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
      <p>Polling: {isRunning ? "Running" : "Stopped"}</p>
      <button onClick={start} disabled={isRunning}>
        Start
      </button>
      <button onClick={stop} disabled={!isRunning}>
        Stop
      </button>
    </div>
  );
}

LinkIconHook

import { useCallback, useEffect, useRef, useState } from "react";
 
interface UsePollingOptions {
  /** interval in ms */
  interval: number;
  /** run once immediately on start */
  immediate?: boolean;
  /** start automatically when hook mounts */
  autoStart?: boolean; 
}
 
type AsyncFunction<T> = () => Promise<T>;
 
/**
 * A reusable polling hook to run an async function at a specified interval.
 * @param asyncFn The async function to execute on each poll.
 * @param options Polling configuration.
 */
export function usePolling<T>(
  asyncFn: AsyncFunction<T>,
  { interval, autoStart = true, immediate = false }: UsePollingOptions
) {
  const [isRunning, setIsRunning] = useState(autoStart);
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<unknown>(null);
  const timerRef = useRef<NodeJS.Timeout | null>(null);
 
  const start = useCallback(() => {
    if (!isRunning) setIsRunning(true);
  }, [isRunning]);
 
  const stop = useCallback(() => {
    setIsRunning(false);
    if (timerRef.current) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  }, []);
 
  const execute = useCallback(async () => {
    try {
      const result = await asyncFn();
      setData(result);
      setError(null);
    } catch (err) {
      setError(err);
      // Log silently to avoid breaking the loop
      console.error("[usePolling] Polling error:", err);
    }
  }, [asyncFn]);
 
  // Core polling loop
  const tick = useCallback(async () => {
    await execute();
    timerRef.current = setTimeout(tick, interval);
  }, [execute, interval]);
 
  // Manage start/stop lifecycle
  useEffect(() => {
    if (isRunning) {
      if (immediate) {
        execute();
      }
      timerRef.current = setTimeout(tick, immediate ? interval : 0);
    }
 
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current);
    };
  }, [isRunning, tick, immediate, execute, interval]);
 
  return { data, error, isRunning, start, stop };
}

LinkIconParameters

NameTypeDefaultDescription
asyncFn() => Promise<T>—The async function to execute on each poll. Must return a promise.
options{ interval: number; immediate?: boolean; autoStart?: boolean; }—Configuration object for polling.
options.intervalnumber—Polling interval in milliseconds.
options.autoStartbooleantrueIf true, polling starts automatically on mount.
options.immediatebooleanfalseIf true, the async function runs immediately before the first interval.

LinkIconReturns

NameTypeDescription
dataT | nullLatest successful result returned by the async function.
errorunknownLast error thrown by the async function, if any.
isRunningbooleanWhether polling is currently active.
start() => voidFunction to manually start polling.
stop() => voidFunction to manually stop polling.