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.
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.
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.
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.
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。