3
Author: Shadeed
Translator: Frontend Xiaozhi
Source: dmitripavlutin

If you have dreams and dry goods, search for [Great Move to the World] Follow this brushing wit who is still doing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.

useEffect() mainly used to manage side effects, such as crawling through the network, directly operating the DOM, and starting and ending timers.

Although useEffect() and useState (methods for managing state) are one of the most commonly used hooks, it takes some time to become familiar and use them correctly.

When using useEffect() , you may encounter a trap, that is the infinite loop of component rendering. In this article, I will talk about common scenarios that produce infinite loops and how to avoid them.

1. Infinite loop and side effects update status

Suppose we have a functional component with a input element in it. The function of the component is to count the input changes to 060ef889e2c618.

We named this component CountInputChanges , and the approximate content is as follows:

function CountInputChanges() {
  const [value, setValue] = useState('');
  const [count, setCount] = useState(-1);

 useEffect(() => setCount(count + 1));
  const onChange = ({ target }) => setValue(target.value);

  return (
    <div>
 <input type="text" value={value} onChange={onChange} />
 <div>Number of changes: {count}</div>
 </div>
  )
}

<input type =“ text” value = {value} onChange = {onChange} /> is a controlled component. value variable holds the input entered by 060ef889e2c6a7. When the user enters the input, the onChange event handler updates the value state.

Here use useEffect() update the count variable. Every time the component is re-rendered due to user input, useEffect(() => setCount(count + 1)) will update the counter.

Because useEffect(() => setCount(count + 1)) is used without dependent parameters, ()=> setCount(count + 1) will execute a callback every time the component is rendered.

Do you think there is a problem with writing this way? Open the demo and try it yourself: https://codesandbox.io/s/infinite-loop-9rb8c?file=/src/App.js

After running, you will find that the count increases uncontrollably, even if you don't input , this is an infinite loop.

image

The problem lies in useEffect() is used:

useEffect(() => setCount(count + 1));

It generates an infinite loop of component re-rendering.

After the initial rendering, useEffect() executes the side effect callback function of the updated state. Status update triggers re-rendering. After re-rendering, useEffect() executes the side effect callback and updates the state again, which will trigger the re-rendering again.

image.png

1.1 Solve by dependency

The infinite loop can useEffect(callback, dependencies) dependency parameter.

Because we want count to increase when the value changes, value as a side-effect dependency.

import { useEffect, useState } from 'react';

function CountInputChanges() {
  const [value, setValue] = useState('');
  const [count, setCount] = useState(-1);

 useEffect(() => setCount(count + 1), [value]);
  const onChange = ({ target }) => setValue(target.value);

  return (
    <div>
 <input type="text" value={value} onChange={onChange} />
 <div>Number of changes: {count}</div>
 </div>
  );
}

Add [value] as a dependency of useEffect , so that the count state variable will only be updated [value] This can solve the infinite loop.

image.png

1.2 Use ref

In addition to dependence, we can also solve this problem useRef()

The idea is that updating the Ref will not trigger the re-rendering of the component.

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

function CountInputChanges() {
  const [value, setValue] = useState("");
  const countRef = useRef(0);

 useEffect(() => countRef.current++);
  const onChange = ({ target }) => setValue(target.value);

  return (
    <div>
 <input type="text" value={value} onChange={onChange} />
 <div>Number of changes: {countRef.current}</div>
 </div>
  );
}

useEffect(() => countRef.current++) time value is re-rendered countRef.current++ , 060ef889e2c8aa will return. The reference change itself does not trigger the component to re-render.

image.png

2. Infinite loops and new object references

Even if the useEffect() dependency is set correctly, be careful when using the object as the dependency.

For example, the following component CountSecrets listens to input . Once the user enters the special word 'secret' , the number of counts of'secret' will increase by 1.

import { useEffect, useState } from "react";

function CountSecrets() {
  const [secret, setSecret] = useState({ value: "", countSecrets: 0 });

  useEffect(() => {
    if (secret.value === 'secret') {
 setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));    }
 }, [secret]);
  const onChange = ({ target }) => {
    setSecret(s => ({ ...s, value: target.value }));
  };

  return (
    <div>
 <input type="text" value={secret.value} onChange={onChange} />
 <div>Number of secrets: {secret.countSecrets}</div>
 </div>
  );
}

Open the demo ( https://codesandbox.io/s/infinite-loop-obj-dependency-7t26v?file=/src/App.js) and try it for yourself. The current input secret , secret.countSecrets is out of control. To grow.

This is an infinite loop problem.

Why is this happening?

secret object is used as useEffect(..., [secret]) . In the side effect callback function, as long as the input value is equal to secret , the update function will be called

setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));

This will increase countSecrets , but it will also create a new object.

secret is now a new object, and the dependencies have changed. So useEffect(..., [secret]) again to update the state and creating a new secret object again, and so on.

Two objects in JavaScript are only equal when they refer to exactly the same object.

2.1 Avoid objects as dependencies

The best way to solve the problem of an infinite loop to create new objects generated by the cycle is to avoid the useEffect() of dependencies use object parameters by reference.

let count = 0;

useEffect(() => {
  // some logic
}, [count]); // Good!
let myObject = {
  prop: 'Value'
};

useEffect(() => {
  // some logic
}, [myObject]); // Not good!
useEffect(() => {
  // some logic
}, [myObject.prop]); // Good!

Fix the infinite loop problem of the <CountSecrets> useEffect(..., [secret])) to useEffect(..., [secret.value]) .

It is enough to call the side-effect callback when secret.value

import { useEffect, useState } from "react";

function CountSecrets() {
  const [secret, setSecret] = useState({ value: "", countSecrets: 0 });

  useEffect(() => {
    if (secret.value === 'secret') {
      setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));
    }
 }, [secret.value]);
  const onChange = ({ target }) => {
    setSecret(s => ({ ...s, value: target.value }));
  };

  return (
    <div>
 <input type="text" value={secret.value} onChange={onChange} />
 <div>Number of secrets: {secret.countSecrets}</div>
 </div>
  );
}

3 summary

useEffect(callback, deps) callback (side effect) after the component is rendered. If you do not pay attention to the effect of side effects, it may trigger an infinite loop of component rendering.

The common case of generating an infinite loop is to update the state in a side effect without specifying any dependent parameters

useEffect(() => {
  // Infinite loop!
  setState(count + 1);
});

An effective way to avoid infinite loops is to set the dependencies correctly:

useEffect(() => {
  // No infinite loop
  setState(count + 1);
}, [whenToUpdateValue]);

In addition, you can also use Ref, updating Ref will not trigger re-rendering:

useEffect(() => {
  // No infinite loop
  countRef.current++;
});

Another common method of infinite loops is to use an object as useEffect() and update that object in a side effect (effectively creating a new object)

useEffect(() => {
  // Infinite loop!
  setObject({
    ...object,
    prop: 'newValue'
  })
}, [object]);

Avoid using objects as dependencies and only use specific properties (the final result should be an original value):

useEffect(() => {
  // No infinite loop
  setObject({
    ...object,
    prop: 'newValue'
  })
}, [object.whenToUpdateProp]);

When using useEffect() , do you know that there are other ways to cause an infinite loop trap?

~End, I’m Xiaozhi, see you next time~


code is deployed, the possible bugs cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful BUG monitoring tool Fundebug .

Original: https://dmitripavlutin.com/react-useeffect-infinite-loop/

communicate with

If you have dreams and dry goods, search on [Moving to the World] Follow this brushing wisdom who is still doing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.


王大冶
68k 声望104.9k 粉丝