How to Create a Timer or Polling in React with setInterval
- 4 minutes read - 851 wordsHave you ever wanted to create a timer in a React app? This could be in support
of a UI timer or polling. In this post, I’ll explain how to create a timer
effect in a React application using hooks and setInterval
.
Let’s get right to the code. Below, I’ll explain what every line is doing.
import * as React from "react";
import { useState } from "react";
const App = () => {
const [timer, setTimer] = useState();
const toggleTimer = () => {
if (timer) {
clearInterval(timer);
setTimer(undefined);
} else {
const timerId = setInterval(() => console.log("tick"), 1000);
setTimer(timerId);
}
};
return <button onClick={toggleTimer}>{timer ? "Stop" : "Start"}</button>;
};
export default App;
Here’s the summary: this code creates a button that reads “Start”. On click,
that changes to “Stop”, creating a timer with setInterval
ticking once per second. This tick behavior could represent anything,
including:
- Advancing a world, such as in Conway’s Game of Life
- Showing how long something takes
Let’s start reading at return
. There, we have a button that shows a stateful call
to action.
import * as React from "react";
import { useState } from "react";
const App = () => {
const [timer, setTimer] = useState();
const toggleTimer = () => {
if (timer) {
clearInterval(timer);
setTimer(undefined);
} else {
const timerId = setInterval(() => console.log("tick"), 1000);
setTimer(timerId);
}
};
return <button onClick={toggleTimer}>{timer ? "Stop" : "Start"}</button>;
};
export default App;
Okay, what’s that state? It’s a timer
, and it’s initially undefined
, shown here
as an empty invocation of useState
:
import * as React from "react";
import { useState } from "react";
const App = () => {
const [timer, setTimer] = useState();
const toggleTimer = () => {
if (timer) {
clearInterval(timer);
setTimer(undefined);
} else {
const timerId = setInterval(() => console.log("tick"), 1000);
setTimer(timerId);
}
};
return <button onClick={toggleTimer}>{timer ? "Stop" : "Start"}</button>;
};
export default App;
More on that in a second!
Click the button, and we call the function toggleTimer
. Because timer
starts as undefined
, our conditional is falsy. So, let’s read the
else
.
import * as React from "react";
import { useState } from "react";
const App = () => {
const [timer, setTimer] = useState(undefined);
const toggleTimer = () => {
if (timer) {
clearInterval(timer);
setTimer(undefined);
} else {
const timerId = setInterval(() => console.log("tick"), 1000);
setTimer(timerId);
}
};
return <button onClick={toggleTimer}>{timer ? "Stop" : "Start"}</button>;
};
export default App;
Here we call setInterval
, passing our ticking function and a delay of 1,000
milliseconds. Every second, our tick fires.
setInterval
has a useful return– an identifier for itself. This will be a
numeric non-zero value like 2202
. We’ll need that later to stop the tick, so
we save it with setTimer
.
Having timer
in state can be really handy; anytime we want to know if the
world is advancing, or the stopwatch is running, etc., the existence of
timer
can answer that question for us. That’s how we’re changing the call to action
from “Start” to “Stop.”
What happens the next time we call the function? timer
is truthy, so we
execute the if
:
import * as React from "react";
import { useState } from "react";
const App = () => {
const [timer, setTimer] = useState(undefined);
const toggleTimer = () => {
if (timer) {
clearInterval(timer);
setTimer(undefined);
} else {
const timerId = setInterval(() => console.log("tick"), 1000);
setTimer(timerId);
}
};
return <button onClick={toggleTimer}>{timer ? "Stop" : "Start"}</button>;
};
export default App;
Here, we use that same identifier, 2022
, to clear the timer. And, we set its
value in state to undefined
.
Polling Example
What if we want this timer to be running without user interaction? Polling a server is
a classic use case for this. useEffect
accomplishes this nicely.
import * as React from "react";
import { useEffect } from "react";
const App = () => {
useEffect(() => {
const timerId = setInterval(() => console.log("tick"), 1000);
return () => {
clearInterval(timerId);
};
}, []);
return <div />;
};
export default App;
We no longer need a state variable to store our timerId
because useEffect
has that in scope. Note the function we return, which makes sure the interval
is cleared when the component unmounts.
import * as React from "react";
import { useEffect } from "react";
const App = () => {
useEffect(() => {
const timerId = setInterval(() => console.log("tick"), 1000);
return () => {
clearInterval(timerId);
};
}, []);
return <div>Timer</div>;
};
export default App;
This can be extracted as a custom hook!
// src/hooks/usePoll.ts
import { useEffect } from "react";
export const usePoll = (callback) => {
useEffect(() => {
const id = setInterval(callback, 3000);
return () => clearInterval(id);
}, [callback]);
};
And its use:
import * as React from "react";
import { useState } from "react";
import { fetchData } from "api/data";
import { usePoll } from "hooks/usePoll";
const App = () => {
const [data, setData] = useState();
usePoll(fetchData(setData));
return <div>{data}</div>;
};
export default App;
That’s all there is to it! I hope you can build upon this simple example. To see this in action, check out the source code to my Conway implementation.