preface
In fact, this problem has been mentioned on Zhihu and Baidu, but there is no detailed article on digging gold, so I am preparing an article to solve how to use timer in useEffect. There is a very good article, if you want to understand more, you can click here to use the React Hooks declaration setInterval
Frowning, the problem is not simple
First let’s do a little Demo, set a state called value, and every 5 seconds, generate a random number and add value to it
import React, { useState, useEffect } from 'react'; import './App.css'; function App() { const [value, setValue] = useState<number>(0); useEffect(() => { const timer: NodeJS.Timeout = setInterval(() => { const random = (Math.random() * 10) | 0; setValue(value + random); }, 5000); return () => { clearInterval(timer); }; } []); return <div>{value}</div>; } export default App;Copy the code
You open your browser with confidence, too Simple! Suddenly you find that the value of value changes only once. You check and find that the original useEffect dependency is not filled in, so you carefully fill in value into the dependency
useEffect(() => {
// .....
}, [value]);
Copy the code
perfect! “The code executed as expected, adding a value every five seconds, thinking, ‘You deserve me! ‘
What you think what you think is not what you think
If we add a timer array to our code, it keeps track of how many timers you have added
function App() {
const [value, setValue] = useState<number>(0);
const [timers, setTimers] = useState<Array<NodeJS.Timeout>>([]);
useEffect(() => {
const timer: NodeJS.Timeout = setInterval(() => {
const random = (Math.random() * 10) | 0;
setValue(value + random);
}, 5000);
timers.push(timer);
setTimers(timers);
console.log(timers);
return () => {
clearInterval(timer);
};
}, [value]);
return <div>{value}</div>;
}
Copy the code
You will be surprised to find that not only do you change the value, but you also generate an extra timer. As shown in the figure below.
The reason for this is that useEffect will not execute effect again when it gets a value of 0 during the first rendering, so setInterval always references the closure value of the first rendering, so it adds a random number to 0 each time instead of summating. Crucially, if you enable Vscode’s eslint plugin, it will also auto-complete dependencies for you
Stale closure issues
Don’t panic, it’s not a problem
To solve this problem, we need to introduce another hook in React,useRef. UseRef returns a mutable ref object whose.current property is initialized as the passed parameter (initialValue). The returned ref object remains constant throughout the life of the component, so we’ll use it in our demo to see how it works
function App() { const [value, setValue] = useState<number>(0); const [timers, setTimers] = useState<Array<NodeJS.Timeout>>([]); const saveCallBack: any = useRef(); const callBack = () => { const random: number = (Math.random() * 10) | 0; setValue(value + random); }; useEffect(() => { saveCallBack.current = callBack; return () => {}; }); useEffect(() => { const tick = () => { saveCallBack.current(); }; const timer: NodeJS.Timeout = setInterval(tick, 5000); timers.push(timer); setTimers(timers); console.log(timers); return () => { clearInterval(timer); }; } []); return <div>{value}</div>; }Copy the code
With useRef, the timer will not be created repeatedly, but the values of the timer will be incremented in order to achieve the desired effect.