头图

Original: https://juejin.cn/post/6972439516703358990
Author: Tonychen

Infinite Chain Of Update

In actual use, sometimes you will encounter Infinite Chain Of Update , which is actually a piece of your code that caused an "infinite loop update". Let's take a look at a few examples 👇

Dependent array problem

For example, when using useEffect , the dependency array is not passed in 👇

// count 会无限 + 1
function App() {
    const [count, setCount] = useState(0)

    useEffect(() => {
        setCount(count + 1)
    })
}

Why is count updated indefinitely? The logic here is like this 👇

  • Component update
  • Execute useEffect
  • Update count and trigger component update
  • Execute useEffect
  • ……

2021-06-09-1053-4.png

The solution is very simple. Just useEffect as the third parameter, and useEffect will not be executed during the next update.

// 正常渲染
function App() {
    const [count, setCount] = useState(0)

    useEffect(() => {
        setCount(count + 1)
    }, [])
}

The updated value is monitored

This is a problem that novice hooks players often encounter, and veterans also have some headaches.

  • Case 1

useEeffect updated state indirectly affects the monitored variables, for example 🌰

function App() {
    const [obj, setObj] = useState({a: 0})
    const {a} = obj
    useEffect(() => {
        setObj({
            ...obj,
            a: 1
        })
    }, [a, obj])
}

The above code will cause an infinite loop when it is actually running. Why? Because the setObj was changed at obj , and useEffect monitored this value, which resulted in an endless loop...

2021-06-09-1053-3.svg

How to solve it? Since obj is caused by the change of infinite loop , in fact, as long as obj is not monitored, there is no such thing.

🤪, where you can make use of setState "callback" usage 👇

function App() {
    const [obj, setObj] = useState({a: 0})
    const {a} = obj;
    useEffect(() => {
        setObj((state) => ({
            ...state,
            a: 1
        }))
    }, [a])
}
  • Case 2

Sometimes you need to decide what the component displays based on different "states", then you usually need to use a state to control the display of several "states". The transition from state 1 to state 2 is asynchronous. A simple way is to use useEffect to monitor it.

2021-06-09-1053-2.svg

If one part of this state depends on external input, the other part is processed according to the state change of this external input. Give an example 👇

export function App({outsider, wait}) {
    const [state, setState] = useState('INIT')
    useEffect(() => {
        // 根据 ousider 处理 state 的值
        if (outsider === true) {
                setState('PENDING')
        } else {
            if (state === 'PENDING') {
                setTimeout(() => {
                    setState('RESOLVED')
                }, wait)
            }
        }
    }, [outsider, state])
    return (
        // 根据 state 来渲染不同的组件/样式
    )
}

In actual operation, it is infinite loop again. Maybe you have the same idea as I thought the first time, which is to use the solution of "Case 1". But note that there is asynchronous processing here, so here can only be said to use useRef to do a simple processing.

export function App({outsider, wait}) {
    const [state, setState] = useState('INIT')
    const stateRef = useRef(state)
    useEffect(() => {
        // 根据 ousider 处理 state 的值
        if (outsider === true) {
            setState('PENDING')
            stateRef.current = 'PENDING'
        } else {
            if (stateRef.current === 'PENDING') {
                setTimeout(() => {
                    setState('RESOLVED')
                    stateRef.current = 'RESOLVED'
                }, wait)
            }
        }
    }, [outsider])
    return (
        // 根据 state 来渲染不同的组件/样式
    )
}

Thus in useEffect you do not need to rely on the state , but also according to state make some operating current value 😄

summary

When writing hooks , you need to always pay attention to whether there is a dependency on state and setState in the code. Usually, the intuitive way of writing will bring infinite loop .

2021-06-09-1053.svg

Can't get the latest value

Newbies hooks often encounter this kind of problem, here is a simple example👇

import { useCallback, useEffect, useState } from "react";

let timeout = null;
export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
    timeout = setTimeout(() => {
      console.log("timeout", count);
      setCount(count + 1);
    }, 1000);
  }, [count]);

  useEffect(() => {
    return () => {
      clearTimeout(timeout);
    };
  }, []);

  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={handleClick}>click me</button>
    </div>
  );
}

After running, you will find that every time console.log prints count + 1 , and this is actually useState implementation of 060c5ad018a6ab, here only a small part of the implementation in the source code is intercepted👇

image.png

It can be seen from useState deconstruction is out of the value of the original data rather than references, so in the example above, the setTimeout get the latest in count value.

Reference

Setting State based on Previous State in useEffect - Its a trap!


tonychen
1.2k 声望272 粉丝