react-tiny-poller
v2.0.2
Published
A tiny React hook to run multiple functions on an interval (default 10s).
Maintainers
Readme
react-tiny-poller
A tiny, powerful React hook to run multiple functions on an interval with enhanced control and error handling.
🚀 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-pollerQuick 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
functionsAsyncFn[]- Array of functions to execute. Can be sync or async functions.optionsOptions(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
intervalprop while the poller is running will tear down the existing timer and restart it. Ifimmediateistrue, the functions are executed again right away on restart (so an interval change may cause an extra immediate run). enabledvsimmediate:enabledis the master on/off switch (no interval exists whileenabledisfalse).immediatecontrols whether a start/restart (initial mount, re-enable, or interval change) triggers an immediate execution before waiting the first delay.runOncesemantics: CallingrunOnce()executes the functions a single time without starting (or resuming) the polling interval and without changingisRunning.- Re‑enable behavior: When
enabledtoggles fromfalsetotrue, the poller starts (and will run immediately ifimmediateistrue). - Concurrency modes:
'parallel'(default): All functions execute simultaneously usingPromise.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 devThen open http://localhost:5173.
Running Tests
npm testBuilding
npm run buildTypeScript
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
