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.