简体   繁体   中英

Prevent infinite loop on function component with default parameter and useEffect

When creating a function component in React and setting a default parameter everything works like expected and the component will be rendered once. But as soon as you add a hook like useEffect and use this parameter in the dependency array the component rerenders forever. I've created a simple demo here: https://codesandbox.io/s/infinite-useeffect-loop-on-default-value-tv7hj?file=/src/TestComponent.jsx

The reason is quite obvious, because when using an object as default parameter, it will be created again and will not be equal to the previous one. And of course this doesn't happen on primitive default parameter values like number or string.

Is there any better way to avoid this side effect besides using defaultProps ?

Yes, instead of setting the default value of value to being an object, just set it to false. Then check if value is truthy, if it is, then access the correct properties, otherwise, just show a default value. New code .

It would be something like:

import { useEffect, useState } from "react";

const TestComponent = ({ value = false }) => {
  const [calcValue, setCalcValue] = useState(0);

  useEffect(() => {
    setCalcValue((cur) => cur + 1);
  }, [value]);

  return (
    <div>
      {value ? value.name : "Test"}:{calcValue}
    </div>
  );
};

The reason you get infinite loops is because the reference of value keeps changing.

The first time the component is rendered, it sees a new reference to value, which triggers the useEffect , which in turns modifies the state of the component, and this leads to a new render, which causes value to be re-created once again because the old reference to that variable has changed.

The easiest way to deal with this is to just create a default value outside the component and use that ( basically the same the as the defaultProps solution ):

import { useEffect, useState } from "react";

const defaultValue = {name: "Test"}; // <-- default here
const TestComponent = ({ value = defaultValue }) => {
  const [calcValue, setCalcValue] = useState(0);

  useEffect(() => {
    setCalcValue((cur) => cur + 1);
  }, [value]);

  return (
    <div>
      {value.name}:{calcValue}
    </div>
  );
};

Doing this will ensure that each time the component renders, it sees the same reference for for value , therefore the useEffect hook only runs once.


Another way of dealing with this is to first wrap your component with memo , then create a new state variable which takes on the original value , and make your useEffect hook depend on this new state variable:

const TestComponent = React.memo(({ value = {name: "Test"} }) => {
  const [calcValue, setCalcValue] = useState(0);
  const [myValue, setMyValue] = useState(value);

  useEffect(() => {
    setCalcValue((cur) => cur + 1);
  }, [myValue]);

  return (
    <div>
      {myValue.name}:{calcValue}
    </div>
  );
});

The reason why we wrap the component with memo is so that it only re-renders after a state change if the prop had changed in value (instead of reference ). You can change the way memo detects props changes by providing a custom comparison function as a second parameter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM