npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-tiny-poller

v2.0.2

Published

A tiny React hook to run multiple functions on an interval (default 10s).

Readme

react-tiny-poller

A tiny, powerful React hook to run multiple functions on an interval with enhanced control and error handling.

npm version TypeScript MIT License

🚀 Live Demo

Try the interactive demo to see all features in action!

Features

  • 🚀 Lightweight - Minimal bundle size with zero dependencies
  • 🔧 TypeScript - Full TypeScript support with type definitions
  • Flexible - Support for both sync and async functions
  • 🎛️ Controllable - Start, stop, and run functions manually
  • 🛡️ Error Handling - Built-in error handling with custom error handlers
  • 🔄 Configurable - Customizable intervals, immediate execution, and more
  • Overlap Prevention - Prevent duplicate executions with configurable overlap handling
  • 🔀 Concurrency Control - Run functions sequentially or in parallel
  • �📦 Dual Package - Supports both ESM and CommonJS

Install

npm install react-tiny-poller

Quick Start

import { usePoller } from "react-tiny-poller";

function App() {
  const { start, stop, runOnce, restart, isRunning } = usePoller([
    () => fetch("/api/users").then(r => r.json()).then(console.log),
    () => fetch("/api/orders").then(r => r.json()).then(console.log),
  ], {
    interval: 10000, // Poll every 10 seconds
    immediate: true  // Run immediately on mount
  });

  return (
    <div>
      <h1>Polling Status: {isRunning ? 'Running' : 'Stopped'}</h1>
      <button onClick={start}>Start Polling</button>
      <button onClick={stop}>Stop Polling</button>
      <button onClick={restart}>Restart Polling</button>
      <button onClick={runOnce}>Run Once</button>
    </div>
  );
}

API Reference

usePoller(functions, options)

Parameters

  • functions AsyncFn[] - Array of functions to execute. Can be sync or async functions.
  • options Options (optional) - Configuration options.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | interval | number | 10000 | Polling interval in milliseconds | | immediate | boolean | true | Whether to run functions immediately on mount | | enabled | boolean | true | Whether to start polling automatically | | onError | (error: Error, fnIndex: number) => void | undefined | Custom error handler | | concurrency | 'sequential' \| 'parallel' | 'parallel' | How to execute multiple functions | | overlap | 'skip' \| 'queue' \| 'allow' | 'allow' | How to handle overlapping executions |

Returns

The hook returns a PollerControls object with the following properties:

| Property | Type | Description | |----------|------|-------------| | start | () => void | Start or resume polling | | stop | () => void | Stop or pause polling | | runOnce | () => Promise<void> | Execute all functions once immediately | | restart | () => void | Restart polling (stop + start) | | isRunning | boolean | Current polling status |

Behavior Notes

  • Interval changes: Updating the interval prop while the poller is running will tear down the existing timer and restart it. If immediate is true, the functions are executed again right away on restart (so an interval change may cause an extra immediate run).
  • enabled vs immediate: enabled is the master on/off switch (no interval exists while enabled is false). immediate controls whether a start/restart (initial mount, re-enable, or interval change) triggers an immediate execution before waiting the first delay.
  • runOnce semantics: Calling runOnce() executes the functions a single time without starting (or resuming) the polling interval and without changing isRunning.
  • Re‑enable behavior: When enabled toggles from false to true, the poller starts (and will run immediately if immediate is true).
  • Concurrency modes:
    • 'parallel' (default): All functions execute simultaneously using Promise.allSettled()
    • 'sequential': Functions execute one after another in array order
  • Overlap handling:
    • 'allow' (default): New executions can start even if previous ones are still running
    • 'skip': Skip new executions if previous execution is still in progress
    • 'queue': Queue new executions to run after current execution completes (not yet implemented)

Examples

Basic Usage

import { usePoller } from "react-tiny-poller";

function BasicExample() {
  usePoller([
    () => console.log("Function 1 executed"),
    () => console.log("Function 2 executed"),
  ]);

  return <div>Check console for polling output</div>;
}

With Async Functions

import { usePoller } from "react-tiny-poller";

function AsyncExample() {
  const [data, setData] = useState(null);

  usePoller([
    async () => {
      const response = await fetch("/api/data");
      const result = await response.json();
      setData(result);
    },
    () => {
      // Mix sync and async functions
      console.log("Sync function executed");
    }
  ], {
    interval: 5000, // Poll every 5 seconds
  });

  return <div>Data: {JSON.stringify(data)}</div>;
}

Manual Control

import { usePoller } from "react-tiny-poller";

function ControlledExample() {
  const { start, stop, runOnce, isRunning } = usePoller([
    () => fetch("/api/health-check"),
  ], {
    enabled: false, // Don't start automatically
    interval: 30000, // 30 seconds
  });

  return (
    <div>
      <p>Status: {isRunning ? "Polling" : "Stopped"}</p>
      <button onClick={start} disabled={isRunning}>
        Start Health Checks
      </button>
      <button onClick={stop} disabled={!isRunning}>
        Stop Health Checks
      </button>
      <button onClick={runOnce}>
        Run Health Check Now
      </button>
    </div>
  );
}

Error Handling

import { usePoller } from "react-tiny-poller";

function ErrorHandlingExample() {
  const [errors, setErrors] = useState([]);

  usePoller([
    () => fetch("/api/might-fail").then(r => r.json()),
    () => {
      if (Math.random() > 0.5) throw new Error("Random error");
    }
  ], {
    interval: 5000,
    onError: (error, fnIndex) => {
      console.error(`Function ${fnIndex} failed:`, error);
      setErrors(prev => [...prev, { fnIndex, error: error.message, time: new Date() }]);
    }
  });

  return (
    <div>
      <h3>Recent Errors:</h3>
      {errors.slice(-5).map((err, i) => (
        <div key={i}>
          Function {err.fnIndex}: {err.error} at {err.time.toLocaleTimeString()}
        </div>
      ))}
    </div>
  );
}

Overlap Prevention

import { usePoller } from "react-tiny-poller";

function OverlapPreventionExample() {
  const [logs, setLogs] = useState([]);

  usePoller([
    async () => {
      const start = Date.now();
      setLogs(prev => [...prev, `Started slow operation at ${new Date().toLocaleTimeString()}`]);

      // Simulate slow operation (3 seconds)
      await new Promise(resolve => setTimeout(resolve, 3000));

      const end = Date.now();
      setLogs(prev => [...prev, `Completed slow operation (took ${end - start}ms)`]);
    }
  ], {
    interval: 1000, // Try to run every second
    overlap: 'skip', // Skip if previous execution is still running
  });

  return (
    <div>
      <h3>Execution Log:</h3>
      {logs.slice(-10).map((log, i) => (
        <div key={i}>{log}</div>
      ))}
    </div>
  );
}

Sequential vs Parallel Execution

import { usePoller } from "react-tiny-poller";

function ConcurrencyExample() {
  const [mode, setMode] = useState('parallel');
  const [logs, setLogs] = useState([]);

  usePoller([
    async () => {
      setLogs(prev => [...prev, `Function 1 started at ${new Date().toLocaleTimeString()}`]);
      await new Promise(resolve => setTimeout(resolve, 1000));
      setLogs(prev => [...prev, `Function 1 completed at ${new Date().toLocaleTimeString()}`]);
    },
    async () => {
      setLogs(prev => [...prev, `Function 2 started at ${new Date().toLocaleTimeString()}`]);
      await new Promise(resolve => setTimeout(resolve, 1500));
      setLogs(prev => [...prev, `Function 2 completed at ${new Date().toLocaleTimeString()}`]);
    }
  ], {
    interval: 5000,
    concurrency: mode, // 'parallel' or 'sequential'
  });

  return (
    <div>
      <div>
        <label>
          <input
            type="radio"
            value="parallel"
            checked={mode === 'parallel'}
            onChange={(e) => setMode(e.target.value)}
          />
          Parallel (functions run simultaneously)
        </label>
        <label>
          <input
            type="radio"
            value="sequential"
            checked={mode === 'sequential'}
            onChange={(e) => setMode(e.target.value)}
          />
          Sequential (functions run one after another)
        </label>
      </div>

      <h3>Execution Log:</h3>
      {logs.slice(-10).map((log, i) => (
        <div key={i}>{log}</div>
      ))}
    </div>
  );
}

Global Polling with PollerProvider

For application-level polling that survives component unmounts and route changes, use the PollerProvider and useGlobalPoller:

Setup

Wrap your app with PollerProvider:

import { PollerProvider } from 'react-tiny-poller';

function App() {
  return (
    <PollerProvider
      autoCleanup={true}    // Auto-cleanup on route changes
      ignoreParams={true}   // Ignore URL parameters
      debug={false}         // Enable debug logging
    >
      <YourAppContent />
    </PollerProvider>
  );
}

Usage

import { useGlobalPoller } from 'react-tiny-poller';

function Page() {
  const [loading, setLoading] = useState(true);

  // ✨ No need for useCallback - functions are automatically memoized!
  const { start, stop, isRunning } = useGlobalPoller([
    () => fetchPageData(),
    () => updateAnalytics()
  ], {
    interval: 5000,
    immediate: false,         // Don't start immediately
    enabled: !loading,        // Only enable when not loading
    url: '/page',        // Optional: custom URL (defaults to current pathname)
    ignoreParams: true,       // Ignore URL parameters when determining route
    overlap: 'skip'           // Prevent overlapping executions
  });

  return (
    <div>
      <h1>Page</h1>
      {loading ? (
        <div>Loading...</div>
      ) : (
        <div>
          <p>Polling Status: {isRunning ? 'Active' : 'Stopped'}</p>
          <button onClick={start}>Start</button>
          <button onClick={stop}>Stop</button>
        </div>
      )}
    </div>
  );
}

Key Benefits

  • Survives Component Unmounts: Polling continues even when components unmount during loading states
  • Route-Based Management: Automatically cleanup pollers when navigating away from routes
  • Global State: Single source of truth for all application-level polling
  • Automatic Cleanup: No memory leaks from forgotten intervals
  • Function Stability: Automatically handles function reference stability - no need for useCallback
  • Debug Support: Built-in logging for development

Handling Loading States

For components with loading states, use enabled: !loading and immediate: false to prevent constant re-rendering:

function Page() {
  const [loading, setLoading] = useState(true);

  const pollingResponse = useGlobalPoller([
    () => getData(),
    () => console.log('Poller running')
  ], {
    interval: 10000,
    immediate: false,    // Don't start immediately
    enabled: !loading,   // Only enable when not loading
    onError: (error) => {
      console.error('Poller error:', error);
    },
  });

  // Your loading logic...

  return loading ? <LoadingSpinner /> : <MainContent />;
}

Development

Running the Example

git clone https://github.com/mp051998/react-tiny-poller.git
cd react-tiny-poller
npm install
npm run dev

Then open http://localhost:5173.

Running Tests

npm test

Building

npm run build

TypeScript

This package is written in TypeScript and includes type definitions. The main types are:

type AsyncFn<T = any> = () => Promise<T> | T;

interface Options {
  interval?: number;
  immediate?: boolean;
  onError?: (error: Error, fnIndex: number) => void;
  enabled?: boolean;
  concurrency?: 'sequential' | 'parallel';
  overlap?: 'skip' | 'queue' | 'allow';
}

interface PollerControls {
  start: () => void;
  stop: () => void;
  runOnce: () => Promise<void>;
  restart: () => void;
  isRunning: boolean;
}

// Global Poller Types
interface GlobalPollerOptions {
  interval?: number;
  immediate?: boolean;
  onError?: (error: Error, fnIndex: number) => void;
  concurrency?: 'sequential' | 'parallel';
  overlap?: 'skip' | 'queue' | 'allow';
  ignoreParams?: boolean;
  url?: string;
  enabled?: boolean;
}

interface GlobalPollerControls {
  start: () => void;
  stop: () => void;
  isRunning: boolean;
  pollerId: string;
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Monish Prakasan