Just to give a different approach, you can define a custom hook for extracting this functionality into a reusable function:
const useInterval = (callback, interval, immediate) => {
const ref = useRef();
// keep reference to callback without restarting the interval
useEffect(() => {
ref.current = callback;
}, [callback]);
useEffect(() => {
// when this flag is set, closure is stale
let cancelled = false;
// wrap callback to pass isCancelled getter as an argument
const fn = () => {
ref.current(() => cancelled);
};
// set interval and run immediately if requested
const id = setInterval(fn, interval);
if (immediate) fn();
// define cleanup logic that runs
// when component is unmounting
// or when or interval or immediate have changed
return () => {
cancelled = true;
clearInterval(id);
};
}, [interval, immediate]);
};
Then you can use the hook like this:
const [temperature, setTemperature] = useState();
useInterval(async (isCancelled) => {
try {
const response = await fetch('urlToWeatherData');
// check for cancellation after each await
// to prevent further action on a stale closure
if (isCancelled()) return;
if (response.status !== 200) {
// throw here to handle errors in catch block
throw new Error(response.statusText);
}
const [{ temperature }] = await response.json();
if (isCancelled()) return;
console.log(temperature);
setTemperature(temperature);
} catch (err) {
console.log('Fetch Error:', err);
}
}, 15000, true);
We can prevent the callback from calling setTemperature()
if the component is unmounted by checking isCancelled()
. For more general use-cases of useInterval()
when the callback is dependent on stateful variables, you should prefer useReducer()
or at least use the functional update form of useState()
.
setInterval()
and then call it immediately before returning the teardown. – Rosel