useTimeout
Execute functions after a specified delay with automatic cleanup and dynamic control in React components.
useTimeout
Execute functions after a specified delay with automatic cleanup and dynamic control in React components.
useTimeout Demo
The message appears below and disappears after 3 seconds usinguseTimeout.
Installation
npx open-hook add useTimeoutyarn dlx open-hook add useTimeoutpnpm dlx open-hook add useTimeoutAPI Reference
Parameters
| Prop | Type | Default |
|---|---|---|
delay? | number | null | - |
callback? | () => void | - |
Returns
| Prop | Type | Default |
|---|---|---|
void? | void | - |
TypeScript Signature
function useTimeout(callback: () => void, delay: number | null): void;Advanced Usage Examples
function ConditionalTimeout() {
const [isActive, setIsActive] = useState(true);
const [message, setMessage] = useState("");
// Only run timeout when isActive is true
useTimeout(
() => {
setMessage("Timeout executed!");
},
isActive ? 2000 : null
);
return (
<div>
<button onClick={() => setIsActive(!isActive)}>
{isActive ? "Cancel" : "Start"} Timeout
</button>
<p>{message}</p>
</div>
);
}function DynamicDelayTimer() {
const [delay, setDelay] = useState(1000);
const [count, setCount] = useState(0);
useTimeout(() => {
setCount((prev) => prev + 1);
}, delay);
// Reset timeout with new delay each time count changes
useEffect(() => {
// This will restart the timeout with the current delay
}, [count]);
return (
<div>
<p>Count: {count}</p>
<input
type="range"
min="500"
max="5000"
value={delay}
onChange={(e) => setDelay(Number(e.target.value))}
/>
<p>Delay: {delay}ms</p>
</div>
);
}function AutoSaveForm() {
const [formData, setFormData] = useState({ title: "", content: "" });
const [lastSaved, setLastSaved] = useState<Date | null>(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const saveForm = useCallback(async () => {
if (hasUnsavedChanges) {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 500));
setLastSaved(new Date());
setHasUnsavedChanges(false);
}
}, [hasUnsavedChanges]);
// Auto-save after 2 seconds of inactivity
useTimeout(saveForm, hasUnsavedChanges ? 2000 : null);
const updateForm = (updates: Partial<typeof formData>) => {
setFormData((prev) => ({ ...prev, ...updates }));
setHasUnsavedChanges(true);
};
return (
<form>
<input
value={formData.title}
onChange={(e) => updateForm({ title: e.target.value })}
placeholder="Title"
/>
<textarea
value={formData.content}
onChange={(e) => updateForm({ content: e.target.value })}
placeholder="Content"
/>
{hasUnsavedChanges && <p>Unsaved changes...</p>}
{lastSaved && <p>Last saved: {lastSaved.toLocaleTimeString()}</p>}
</form>
);
}function ToastNotification() {
const [toasts, setToasts] = useState<Array<{ id: string; message: string }>>(
[]
);
const addToast = (message: string) => {
const id = Math.random().toString(36);
setToasts((prev) => [...prev, { id, message }]);
};
const removeToast = (id: string) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
return (
<div>
<button onClick={() => addToast("New notification!")}>Add Toast</button>
<div className="toast-container">
{toasts.map((toast) => (
<Toast
key={toast.id}
message={toast.message}
onRemove={() => removeToast(toast.id)}
/>
))}
</div>
</div>
);
}
function Toast({
message,
onRemove,
}: {
message: string;
onRemove: () => void;
}) {
// Auto-remove toast after 3 seconds
useTimeout(onRemove, 3000);
return (
<div className="toast">
{message}
<button onClick={onRemove}>×</button>
</div>
);
}Best Practices
Use useTimeout for delayed actions with automatic cleanup:
// ✅ Good - Clear timeout intentions
useTimeout(() => {
showNotification("Welcome!");
}, 1000);
// ✅ Good - Conditional execution
useTimeout(
() => {
autoSave();
},
hasUnsavedChanges ? 5000 : null
);
// ❌ Avoid - Complex logic in timeout
useTimeout(() => {
// Too much logic here
fetchData().then(processData).then(updateUI);
}, 1000);Performance Guidelines
- 🎯 Single Purpose: Keep timeout callbacks focused and simple
- 🧹 Automatic Cleanup: The hook handles cleanup automatically
- 🔄 Dynamic Control: Use null delay to cancel timeouts
- ⚡ Callback Stability: Use useCallback for complex callback functions
Do's and Don'ts
- ✅ Use for delayed notifications and messages
- ✅ Implement auto-save functionality
- ✅ Create timed UI animations
- ✅ Handle delayed API calls
- ❌ Don't use for intervals (use setInterval instead)
- ❌ Avoid heavy computations in callbacks
- ❌ Don't forget to handle component unmounting
- ❌ Avoid setting very short delays (< 50ms)
Performance Metrics
| Metric | Value | Description |
|---|---|---|
| Bundle Size | ~0.5kb | Minified + gzipped |
| Memory Usage | Minimal | Single timeout reference |
| Cleanup | Automatic | Clears timeout on unmount |
| Performance | Excellent | No performance overhead |
Browser Compatibility
| Browser | Supported | Notes |
|---|---|---|
| Chrome (latest) | ✅Yes | Full support |
| Firefox (latest) | ✅Yes | Full support |
| Safari (latest) | ✅Yes | Full support |
| Edge (latest) | ✅Yes | Full support |
| IE 11 | ❌No | useEffect/useRef required |
| Node.js | ✅Yes | SSR compatible |
Use Cases
Delayed Actions
Execute functions after a specific delay with automatic cleanup.
Auto-Save Features
Implement automatic saving after periods of user inactivity.
Toast Notifications
Create self-dismissing notifications and alerts.
UI Animations
Trigger delayed animations and transitions.
Accessibility
Accessibility Considerations
- Use timeouts for non-critical UI updates only - Provide ways to extend or cancel important timeouts - Announce timeout-based changes to screen readers - Consider users with cognitive disabilities who may need more time
Troubleshooting
Internals
How It Works
The hook uses useRef to store the latest callback function and useEffect to manage the setTimeout. When the delay changes, it clears the previous timeout and sets a new one. The callback ref ensures that the timeout always executes the most recent version of the callback function.
The cleanup function in useEffect automatically clears the timeout when the component unmounts or the delay changes.
Testing Example
import { renderHook, act } from "@testing-library/react";
import { useTimeout } from "./useTimeout";
// Mock timers
jest.useFakeTimers();
describe("useTimeout", () => {
it("should execute callback after delay", () => {
const callback = jest.fn();
renderHook(() => useTimeout(callback, 1000));
expect(callback).not.toHaveBeenCalled();
act(() => {
jest.advanceTimersByTime(1000);
});
expect(callback).toHaveBeenCalledTimes(1);
});
it("should not execute when delay is null", () => {
const callback = jest.fn();
renderHook(() => useTimeout(callback, null));
act(() => {
jest.advanceTimersByTime(5000);
});
expect(callback).not.toHaveBeenCalled();
});
it("should clear timeout on unmount", () => {
const callback = jest.fn();
const { unmount } = renderHook(() => useTimeout(callback, 1000));
unmount();
act(() => {
jest.advanceTimersByTime(1000);
});
expect(callback).not.toHaveBeenCalled();
});
it("should restart timeout when delay changes", () => {
const callback = jest.fn();
const { rerender } = renderHook(
({ delay }) => useTimeout(callback, delay),
{ initialProps: { delay: 1000 } }
);
act(() => {
jest.advanceTimersByTime(500);
});
rerender({ delay: 2000 });
act(() => {
jest.advanceTimersByTime(1500);
});
expect(callback).not.toHaveBeenCalled();
act(() => {
jest.advanceTimersByTime(500);
});
expect(callback).toHaveBeenCalledTimes(1);
});
});FAQ
Related Hooks
- useDebounce - Debounce value changes with timeout
- useLocalStorage - Auto-save to localStorage
- usePrevious - Track changes that trigger timeouts
Changelog
- 2.0.0 — Enhanced TypeScript support, improved callback handling, comprehensive documentation and examples
- 1.1.0 — Added null delay support for conditional timeouts
- 1.0.0 — Initial release with basic timeout functionality