1. Error recurrence
The development environment reported the following error.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Call Stack
checkForNestedUpdates
website/./node_modules/react-dom/cjs/react-dom.development.js:4013:321
scheduleUpdateOnFiber
website/./node_modules/react-dom/cjs/react-dom.development.js:3606:187
dispatchAction
website/./node_modules/react-dom/cjs/react-dom.development.js:2689:115
eval
website/./src/components/FileUpload.jsx:73:7
invokePassiveEffectCreate
website/./node_modules/react-dom/cjs/react-dom.development.js:3960:1047
HTMLUnknownElement.callCallback
website/./node_modules/react-dom/cjs/react-dom.development.js:657:119
Object.invokeGuardedCallbackDev
website/./node_modules/react-dom/cjs/react-dom.development.js:677:45
invokeGuardedCallback
website/./node_modules/react-dom/cjs/react-dom.development.js:696:126
flushPassiveEffectsImpl
website/./node_modules/react-dom/cjs/react-dom.development.js:3968:212
unstable_runWithPriority
website/./node_modules/scheduler/cjs/scheduler.development.js:465:16
2. Troubleshooting
- By commenting the code, I found that the problem was
Assets
the referenced in the componentFileUpload
there was a problem. It happens that theFileUpload
component has been modified recently. - Compare the git records through sourcetree, see
FileUpload
What components have been modified? As shown below. - Then compare the description in the error message, among which
componentWillUpdate or componentDidUpdate
, presumably refers to the newly addeduseEffect
code snippet. - Comment out the above
useEffect
code snippet, and the error disappears.
3. Reason Analysis
The properties of useEffect
indicate that as long as initFiles
changes, lines 46-48 will execute.
Since the above useEffect
code snippet actually causes an infinite loop, it also makes a point:
-
setFileList(initFiles)
changedinitFiles
, so that the function inuseEffect
is called again.
So, initFiles
exactly what changes did it go through to make the call happen repeatedly?
Output fileList
and initFiles
:
console.log(fileList === initFiles)
It can be found that only the output of the first render is true
, and all subsequent ones are false
.
- 第
true
,useState
的入参array
时,只是简单的赋值关系,fileList
initFiles
specifies the same memory address. -
setFileList
浅拷贝,fileList
,改变了fileList
,也就是改变了最新initFiles
the memory points to. At the same time, React retains the previous value ofinitFiles
for dependency comparison. -
useEffect
When comparing the dependencies of reference types, such asobject/array
, the simple===
operator is used, that is to say, whether the memory addresses are consistent. - Two times before and after
initFiles
although the internal data is the same, but the memory points are different, it is considered byuseEffect
[dependency has changed], resulting in an infinite loop.
4. Solution 1
Try not to use object or array directly as dependencies, but use value types instead of reference types
useEffect(() => { //... }, [initFiles.length])
5. Solution 2
Is it possible to copy initFiles
useState
?
const [fileList, setFileList] = useState([...initFiles])
useEffect(() => {
if (fileList.length === 0) {
setFileList([...initFiles])
}
}, [initFiles])
This will still report the same infinite loop error, why is this?
initFiles
is passed in from the parent component, will it be when the FileUpload component is re-rendered, initFiles
has been re-assigned? The next two demos prove this conjecture.
- Demo1 - Open with caution. After opening, the browser tab will be stuck: When initFiles is initialized, use
[]
as the default value, resulting in an infinite loop. - Demo1 - Open with confidence. After opening, JS will not be executed, and the browser will not be stuck. You can view the code with confidence.
- Demo2 : When initFiles is initialized, the default value is not used, and the parent component is not updated, resulting in no dead loop.
In Demo1, initFiles
as a prop
will be assigned to a new empty array every time it is rendered, changing its memory pointer. Causes useEffect
to execute continuously.
const FileUpload = ({initFiles=[]}) => {}
In Demo2, the value of initFiles
is completely passed in by the parent component. When the variable of the parent component does not change, initFiles
does not change.
const FileUpload = ({initFiles=[]}) => {}
const App = () => {
return <FileUpload initFiles={[]} />
}
That is to say, as long as initFiles
is guaranteed not to be assigned cyclically, an infinite loop can be avoided.
6. Conclusion
It is not recommended to use a reference type such as array/object
as a dependency of useEffect
, because it is very likely to trigger bugs due to cheating, and it is difficult to troubleshoot errors.
It is recommended to use one or more value types as useEffect
dependencies.
Follow the official account to learn more about the practice of React/Vue.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。