头图

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

  1. By commenting the code, I found that the problem was Assets the referenced in the component FileUpload there was a problem. It happens that the FileUpload component has been modified recently.
  2. Compare the git records through sourcetree, see FileUpload What components have been modified? As shown below.
  3. Then compare the description in the error message, among which componentWillUpdate or componentDidUpdate , presumably refers to the newly added useEffect code snippet.
  4. 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) changed initFiles , so that the function in useEffect 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 .

  • trueuseState的入参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 of initFiles for dependency comparison.
  • useEffect When comparing the dependencies of reference types, such as object/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 by useEffect [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.


jinling
455 声望25 粉丝

RunJS在线编辑器开发者。[链接]