Declarative timer management hooks for intervals, timeouts, countdown functionality, and animation frame-based timing with automatic cleanup and control.
Declarative setInterval with automatic cleanup and control methods.
/**
* Declarative setInterval with automatic cleanup
* @param fn - Function to execute repeatedly
* @param delay - Interval delay in milliseconds (null to pause)
* @param options - Configuration options
* @returns Function to clear the interval
*/
function useInterval(
fn: () => void,
delay?: number,
options?: { immediate?: boolean }
): () => void;Usage Example:
import { useInterval } from 'ahooks';
import { useState } from 'react';
function IntervalExample() {
const [count, setCount] = useState(0);
const [delay, setDelay] = useState<number | null>(1000);
const [isRunning, setIsRunning] = useState(true);
// Basic interval that increments counter
const clearCounter = useInterval(() => {
setCount(c => c + 1);
}, isRunning ? delay : null); // Pass null to pause
// Interval with immediate execution
const [timer, setTimer] = useState(0);
useInterval(() => {
setTimer(t => t + 1);
}, 500, { immediate: true }); // Runs immediately, then every 500ms
// Dynamic interval speed
const [speed, setSpeed] = useState(1000);
const [message, setMessage] = useState('');
useInterval(() => {
const messages = ['Hello', 'World', 'from', 'ahooks!'];
setMessage(messages[Math.floor(Math.random() * messages.length)]);
}, speed);
return (
<div>
<h2>Interval Examples</h2>
<div>
<h3>Basic Counter</h3>
<p>Count: {count}</p>
<p>Status: {isRunning ? 'Running' : 'Paused'}</p>
<button onClick={() => setIsRunning(!isRunning)}>
{isRunning ? 'Pause' : 'Resume'}
</button>
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={clearCounter}>Stop</button>
<div>
<label>Delay (ms): </label>
<input
type="number"
value={delay || ''}
onChange={(e) => setDelay(e.target.value ? parseInt(e.target.value) : null)}
/>
</div>
</div>
<div>
<h3>Immediate Timer</h3>
<p>Timer (with immediate): {timer}</p>
</div>
<div>
<h3>Dynamic Speed Message</h3>
<p>Message: {message}</p>
<label>Speed (ms): </label>
<input
type="range"
min="100"
max="2000"
value={speed}
onChange={(e) => setSpeed(parseInt(e.target.value))}
/>
<span>{speed}ms</span>
</div>
</div>
);
}Real-time Data Polling:
import { useInterval } from 'ahooks';
import { useState, useRef } from 'react';
function DataPolling() {
const [data, setData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const [isPolling, setIsPolling] = useState(true);
const retryCount = useRef(0);
// Poll data every 5 seconds
useInterval(async () => {
try {
const response = await fetch('/api/realtime-data');
if (!response.ok) throw new Error('Failed to fetch');
const newData = await response.json();
setData(newData);
setError(null);
retryCount.current = 0; // Reset retry count on success
} catch (err) {
retryCount.current++;
setError(`Failed to fetch data (attempt ${retryCount.current})`);
// Stop polling after 3 failed attempts
if (retryCount.current >= 3) {
setIsPolling(false);
}
}
}, isPolling ? 5000 : null);
return (
<div>
<h3>Real-time Data Polling</h3>
<button onClick={() => {
setIsPolling(!isPolling);
if (!isPolling) retryCount.current = 0; // Reset on restart
}}>
{isPolling ? 'Stop Polling' : 'Start Polling'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}Declarative setTimeout with automatic cleanup.
/**
* Declarative setTimeout with automatic cleanup
* @param fn - Function to execute after delay
* @param delay - Timeout delay in milliseconds (null to cancel)
*/
function useTimeout(fn: () => void, delay?: number): void;Usage Example:
import { useTimeout } from 'ahooks';
import { useState } from 'react';
function TimeoutExample() {
const [message, setMessage] = useState('');
const [showNotification, setShowNotification] = useState(false);
// Auto-hide notification after 3 seconds
useTimeout(() => {
setShowNotification(false);
}, showNotification ? 3000 : null);
// Delayed message display
const [delayedMessage, setDelayedMessage] = useState('');
const [inputValue, setInputValue] = useState('');
useTimeout(() => {
if (inputValue) {
setDelayedMessage(`You typed: ${inputValue}`);
}
}, inputValue ? 2000 : null); // Show message 2 seconds after typing
const showNotificationHandler = () => {
setShowNotification(true);
setMessage('This notification will disappear in 3 seconds!');
};
return (
<div>
<h2>Timeout Examples</h2>
<div>
<h3>Auto-hide Notification</h3>
<button onClick={showNotificationHandler}>
Show Notification
</button>
{showNotification && (
<div style={{
padding: '10px',
backgroundColor: 'lightgreen',
border: '1px solid green',
marginTop: '10px'
}}>
{message}
</div>
)}
</div>
<div>
<h3>Delayed Message</h3>
<input
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
setDelayedMessage(''); // Clear previous message
}}
placeholder="Type something..."
/>
{delayedMessage && (
<p style={{ fontStyle: 'italic', color: 'blue' }}>
{delayedMessage}
</p>
)}
</div>
</div>
);
}Uses requestAnimationFrame for smooth intervals, ideal for animations and high-frequency updates.
/**
* Uses requestAnimationFrame for smooth intervals
* Better for animations than regular setInterval
* @param fn - Function to execute repeatedly
* @param delay - Interval delay in milliseconds (null to pause)
* @param options - Configuration options
* @returns Function to clear the interval
*/
function useRafInterval(
fn: () => void,
delay?: number,
options?: { immediate?: boolean }
): () => void;Uses requestAnimationFrame for timeouts, providing better timing accuracy for animations.
/**
* Uses requestAnimationFrame for timeouts
* More accurate timing for animations than regular setTimeout
* @param fn - Function to execute after delay
* @param delay - Timeout delay in milliseconds (null to cancel)
*/
function useRafTimeout(fn: () => void, delay?: number): void;Animation Example:
import { useRafInterval, useRafTimeout } from 'ahooks';
import { useState, useRef } from 'react';
function AnimationExample() {
const [position, setPosition] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const [rotation, setRotation] = useState(0);
const direction = useRef(1);
// Smooth animation using RAF interval
useRafInterval(() => {
setPosition(pos => {
const newPos = pos + (2 * direction.current);
// Bounce at boundaries
if (newPos >= 300 || newPos <= 0) {
direction.current *= -1;
}
return Math.max(0, Math.min(300, newPos));
});
setRotation(rot => (rot + 5) % 360);
}, isAnimating ? 16 : null); // ~60fps (16ms intervals)
// Auto-stop animation after 5 seconds
useRafTimeout(() => {
setIsAnimating(false);
}, isAnimating ? 5000 : null);
// Particle system animation
const [particles, setParticles] = useState<Array<{
id: number;
x: number;
y: number;
vx: number;
vy: number;
}>>([]);
const [showParticles, setShowParticles] = useState(false);
// Update particles using RAF for smooth animation
useRafInterval(() => {
setParticles(prevParticles =>
prevParticles
.map(particle => ({
...particle,
x: particle.x + particle.vx,
y: particle.y + particle.vy,
vy: particle.vy + 0.5, // Gravity
}))
.filter(particle => particle.y < 400) // Remove particles that fell off
);
}, showParticles ? 16 : null);
const createParticles = () => {
const newParticles = Array.from({ length: 20 }, (_, i) => ({
id: Date.now() + i,
x: 200 + Math.random() * 100,
y: 50,
vx: (Math.random() - 0.5) * 10,
vy: Math.random() * -15 - 5,
}));
setParticles(newParticles);
setShowParticles(true);
// Auto-stop after 3 seconds
setTimeout(() => setShowParticles(false), 3000);
};
return (
<div>
<h2>RAF Animation Examples</h2>
<div style={{ position: 'relative', height: '100px', border: '1px solid #ccc', margin: '20px 0' }}>
<div
style={{
position: 'absolute',
left: position,
top: '40px',
width: '20px',
height: '20px',
backgroundColor: 'red',
borderRadius: '50%',
transform: `rotate(${rotation}deg)`,
transition: 'none' // No CSS transitions, pure JS animation
}}
/>
</div>
<div>
<button onClick={() => setIsAnimating(!isAnimating)}>
{isAnimating ? 'Stop' : 'Start'} Bouncing Ball
</button>
<span style={{ marginLeft: '10px' }}>
{isAnimating && 'Will auto-stop in 5 seconds'}
</span>
</div>
<div style={{ position: 'relative', height: '400px', border: '1px solid #ccc', margin: '20px 0', overflow: 'hidden' }}>
<button
onClick={createParticles}
style={{ position: 'absolute', top: '10px', left: '10px' }}
>
Create Particle Explosion
</button>
{particles.map(particle => (
<div
key={particle.id}
style={{
position: 'absolute',
left: particle.x,
top: particle.y,
width: '4px',
height: '4px',
backgroundColor: 'blue',
borderRadius: '50%'
}}
/>
))}
</div>
</div>
);
}Advanced countdown timer with formatting and customizable intervals.
/**
* Advanced countdown timer with formatting
* @param options - Configuration options
* @returns Array with remaining time and formatted time object
*/
function useCountDown(options?: Options): [number, FormattedRes];
interface Options {
/** Initial countdown time in milliseconds */
leftTime?: number;
/** Target date to countdown to */
targetDate?: TDate;
/** Update interval in milliseconds (default: 1000) */
interval?: number;
/** Callback when countdown reaches zero */
onEnd?: () => void;
}
type TDate = Date | number | string | undefined;
interface FormattedRes {
/** Days remaining */
days: number;
/** Hours remaining (0-23) */
hours: number;
/** Minutes remaining (0-59) */
minutes: number;
/** Seconds remaining (0-59) */
seconds: number;
/** Milliseconds remaining (0-999) */
milliseconds: number;
}Usage Example:
import { useCountDown } from 'ahooks';
import { useState } from 'react';
function CountdownExample() {
// Countdown from 10 seconds
const [countdown1, formatted1] = useCountDown({
leftTime: 10 * 1000,
onEnd: () => alert('Countdown 1 finished!')
});
// Countdown to specific date
const targetDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours from now
const [countdown2, formatted2] = useCountDown({
targetDate,
interval: 1000,
onEnd: () => console.log('Event started!')
});
// Pomodoro timer (25 minutes)
const [pomodoroTime, setPomodoroTime] = useState(25 * 60 * 1000);
const [isWorking, setIsWorking] = useState(true);
const [pomodoro, formattedPomodoro] = useCountDown({
leftTime: pomodoroTime,
onEnd: () => {
if (isWorking) {
// Work session ended, start break
setPomodoroTime(5 * 60 * 1000); // 5 minute break
setIsWorking(false);
alert('Work session complete! Take a 5-minute break.');
} else {
// Break ended, start work
setPomodoroTime(25 * 60 * 1000); // 25 minute work
setIsWorking(true);
alert('Break over! Start your next work session.');
}
}
});
// High precision countdown (updates every 100ms)
const [preciseCountdown, preciseFormatted] = useCountDown({
leftTime: 30 * 1000, // 30 seconds
interval: 100, // Update every 100ms
onEnd: () => console.log('Precise countdown finished')
});
return (
<div>
<h2>Countdown Examples</h2>
<div>
<h3>Simple 10-Second Countdown</h3>
<div style={{ fontSize: '24px', fontFamily: 'monospace' }}>
{countdown1 > 0 ? (
`${formatted1.minutes.toString().padStart(2, '0')}:${formatted1.seconds.toString().padStart(2, '0')}`
) : (
'FINISHED!'
)}
</div>
<p>Remaining: {countdown1}ms</p>
</div>
<div>
<h3>Countdown to Event</h3>
<div style={{ fontSize: '18px', fontFamily: 'monospace' }}>
{countdown2 > 0 ? (
<span>
{formatted2.days}d {formatted2.hours}h {formatted2.minutes}m {formatted2.seconds}s
</span>
) : (
'EVENT STARTED!'
)}
</div>
<p>Target: {targetDate.toLocaleString()}</p>
</div>
<div>
<h3>Pomodoro Timer</h3>
<div style={{
fontSize: '32px',
fontFamily: 'monospace',
color: isWorking ? '#e74c3c' : '#27ae60'
}}>
{formattedPomodoro.minutes.toString().padStart(2, '0')}:
{formattedPomodoro.seconds.toString().padStart(2, '0')}
</div>
<p>Mode: {isWorking ? 'Work Session' : 'Break Time'}</p>
<div>
<button onClick={() => {
setPomodoroTime(25 * 60 * 1000);
setIsWorking(true);
}}>
Start Work (25min)
</button>
<button onClick={() => {
setPomodoroTime(5 * 60 * 1000);
setIsWorking(false);
}}>
Start Break (5min)
</button>
</div>
</div>
<div>
<h3>High Precision Countdown</h3>
<div style={{ fontSize: '20px', fontFamily: 'monospace' }}>
{preciseCountdown > 0 ? (
<span>
{preciseFormatted.seconds}.{Math.floor(preciseFormatted.milliseconds / 100)}s
</span>
) : (
'DONE!'
)}
</div>
<p>Updates every 100ms for smooth animation</p>
</div>
</div>
);
}Live Event Countdown:
import { useCountDown } from 'ahooks';
interface Event {
name: string;
date: Date;
description: string;
}
function EventCountdown({ events }: { events: Event[] }) {
return (
<div>
<h2>Upcoming Events</h2>
{events.map((event, index) => {
const [timeLeft, formatted] = useCountDown({
targetDate: event.date,
onEnd: () => {
// Could trigger notification, confetti, etc.
console.log(`${event.name} has started!`);
}
});
const isLive = timeLeft <= 0;
return (
<div
key={index}
style={{
border: '1px solid #ddd',
padding: '15px',
margin: '10px 0',
borderRadius: '8px',
backgroundColor: isLive ? '#ffe6e6' : '#f9f9f9'
}}
>
<h3>{event.name}</h3>
<p>{event.description}</p>
<p>Scheduled: {event.date.toLocaleString()}</p>
{isLive ? (
<div style={{ color: 'red', fontWeight: 'bold', fontSize: '18px' }}>
🔴 LIVE NOW!
</div>
) : (
<div style={{ fontSize: '16px', fontFamily: 'monospace' }}>
Starts in: {formatted.days}d {formatted.hours}h {formatted.minutes}m {formatted.seconds}s
</div>
)}
</div>
);
})}
</div>
);
}null or undefined as delay pauses the timer// Date types
type TDate = Date | number | string | undefined;
// Countdown formatting result
interface FormattedRes {
days: number;
hours: number;
minutes: number;
seconds: number;
milliseconds: number;
}