Using a Timer in a React Component

last updated: Nov 3rd, 2020

Sometimes you might need to write a component that relies on time. Perhaps you need to change some state after a period of time. Or maybe you need to perform a change that repeats on a regular interval.

Adding time-based changes to your React components is easy to perform in stateless functional components using a few React hooks. I'm going to show you how.

AutoCounter

Let's create a component that automatically counts up. Here is a partial implementation that contains the count but doesn't count up yet.

export const AutoCounter = () => {    const [ count, setCount ] = useState(0);    return (        <div>            { count }        </div>    );};

As you can see, it contains some internal state to hold the value of the counter and it's initialized to zero. Next, we'll add a timer.

setInterval

JavaScript has a method setInterval() that lets us execute a function every N milliseconds. We're going to call it and capture the return value, which is a handle to the timer. The return value can be used to cancel the timer -- we'll need that later on.

useRef

We're going to use useRef() from React to hold the timer handle. This lets us keep a mutable value (the timer handle) in an immutable container.

"Hey, why not use useState() to hold the timer handle?" you might ask... Well sure, you could. But then anything that depends on the timer would be recalculated each time the timer is set. It's just cleaner to use an immutable ref so that there are no unnecessary redraws.

useEffect

Next, we're going to use useEffect() from React to start and stop the timer. useEffect takes a function and an array of dependencies. The function is executed every time one of the dependencies changes.

We're going to pass it a function to start our timer; and the dependency array will consist of our immutable timer ref and the setCount() state setter. Neither the immutable ref nor the setCount function ever change so our timer start function will only execute once -- when the component is mounted.

Furthermore, the function we pass into useEffect can itself return a function that cleans up when the dependencies change. Take a look at the code below.

Putting it all together

export const AutoCounter = () => {    const [ count, setCount ] = useState(0);    const timerRef = useRef<NodeJS.Timeout>();    // This executes once upon mounting    // i.e. equivalent to componentDidMount() in a class component    useEffect(() => {        // Register a function to increment the count every second        // and capture the timer handle so we can cancel it later.        timerRef.current = setInterval(() => {            setCount(c => c + 1);        }, 1000);        // Clean up when the component is unmounting        // i.e. equivalent to componentWillUnmount() in a class        return () => {            clearInterval(timerRef.current!);        };    }, [ timerRef, setCount ]);    return (        <div>            { count }        </div>    );};

Level up Your React + Redux + TypeScript

with articles, tutorials, sample code, and Q&A.