javascript 48 lines · 8 steps

Building a useCountdown hook in React

A custom React hook that drives a ticking countdown timer with start, pause, and reset controls.

Explained by highlit
1import { useState, useEffect, useCallback } from 'react';
2 
3export function useCountdown(initialSeconds) {
4 const [secondsLeft, setSecondsLeft] = useState(initialSeconds);
5 const [isRunning, setIsRunning] = useState(false);
6 
7 useEffect(() => {
8 if (!isRunning) return;
9 
10 const id = setInterval(() => {
11 setSecondsLeft((prev) => {
12 if (prev <= 1) {
13 clearInterval(id);
14 setIsRunning(false);
15 return 0;
16 }
17 return prev - 1;
18 });
19 }, 1000);
20 
21 return () => clearInterval(id);
22 }, [isRunning]);
23 
24 const start = useCallback(() => {
25 setSecondsLeft((prev) => (prev > 0 ? prev : initialSeconds));
26 setIsRunning(true);
27 }, [initialSeconds]);
28 
29 const pause = useCallback(() => setIsRunning(false), []);
30 
31 const reset = useCallback(() => {
32 setIsRunning(false);
33 setSecondsLeft(initialSeconds);
34 }, [initialSeconds]);
35 
36 const minutes = String(Math.floor(secondsLeft / 60)).padStart(2, '0');
37 const seconds = String(secondsLeft % 60).padStart(2, '0');
38 
39 return {
40 secondsLeft,
41 isRunning,
42 isFinished: secondsLeft === 0,
43 display: `${minutes}:${seconds}`,
44 start,
45 pause,
46 reset,
47 };
48}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Gating a useEffect on a boolean state lets you start and stop side effects declaratively instead of imperatively.
  2. 2Returning a cleanup function from useEffect guarantees intervals are cleared on unmount or dependency change.
  3. 3A custom hook can package internal state, effects, and derived values into one clean API for components.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a useCountdown hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code