useEffect() is used to introduce operations with "side effects", such as AJAX request, DOM operation, start and end countdown, monitoring and contact events, which can be done on the useEffect hook. So, is it really so simple that it can be used directly? No, you may encounter a trap, which is the infinite loop of component rendering. This article will introduce the common scenarios of infinite loop and how to avoid it in detail.
side effect
Side effects refer to the additional impact on the main calling function in addition to the return function value when calling the function. Some built-in functions of JS have side effects, such as:
[1,2,3].shift(); // After the shift function is executed, one element will be missing from the original data
Infinite loop
import React, { Fragment, useState } from 'react'; function countChange() { const [value, setValue] = useState(''); const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }); return ( <Fragment> <input type="text" value={value} onChange={({ target }) => { setValue(target.value); }} /> <div>count is {count}</div> </Fragment> ); }
In the above example, the value will be updated when the "input" box is input, and the page will be re rendered at this time, because the "useEffect" does not depend on parameters. At this time, the side effect callback will be executed every time the "input" box is input, and the count will be updated every time, so the callback will be executed again; Trapped in an infinite loop.
At this time, you only need to add a dependency on "useEffect". Only when the value of value is updated, can you execute the side effect callback. Avoid infinite loops; When the dependency is an empty array, it means that it is called only once in the first rendering;
useEffect(() => { setCount(count + 1); }, [value]);
Arrays, objects, functions as dependencies
"useEffect" triggers the callback only when the dependency changes, and compares whether it changes through shallow objects; What happens if you use objects or data as dependencies?
import React, { Fragment, useState } from 'react'; function countChange() { const [value, setValue] = useState(''); const [count, setCount] = useState(0); const dep = ['dep']; const obj = { name: 'pp', }; // Using arrays as dependencies useEffect(() => { setCount(count + 1); }, [dep]); // Using objects as dependencies useEffect(() => { setCount(count + 1); }, [obj]); return ( <Fragment> <input type="text" value={value} onChange={({ target }) => { setValue(target.value); }} /> <div>count is {count}</div> </Fragment> ); }
Due to the relationship of shallow comparison, the comparison result is always false. Whether it is an array or an object as a dependency, it will trigger callback again and again; Causes an infinite loop.
The array as an object can be solved through "useRef". Changing the reference itself will not trigger the component to re render. The corresponding code is changed to:
const { current: dep } = useRef(['dep']); useEffect(() => { setCount(count + 1); }, [dep]);
Objects can be solved through "useMemo" as objects. The memorized values will be recalculated only when the dependency changes. The corresponding code is changed to:
const obj = useMemo(() => ({ name: 'pp', }), []) useEffect(() => { setCount(count + 1); }, [obj]);
Functions as dependencies will also lead to infinite loops. No code will be posted here; We can solve it through "useCallback"; "useCallback" returns a memoized version of callback, which will change only when the dependency changes.
const func = useCallback(() => { return '1'; }, []);
summary
The Function of "useEffect" is very powerful, but unimaginable problems will occur if it is not used properly. Therefore, we must use "useEffect" correctly. If the dependent values of the dependent Array of useEffect are reference data such as Object, Array and Function, you should pay attention to it.