admin管理员组

文章数量:1316329

I went through several questions on SO regarding default props for functional ponents and they all remend using ES6 default parameters. Here are links to those questions.

  • React - defaultProps vs ES6 default params when destructuring (performances issues)
  • React functional ponent default props vs default parameters

However, when I use that method for writing ponents with effects running on props change, I get unwanted behaviour with non-primitives. For example, the following code will result in an infinite loop.

const Parent = () => {
  let somethingUndefined;

  return (
    <div>
      <Child prop={somethingUndefined} />
    </div>
  );
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

ReactDOM.render(<Parent />, document.getElementsByTagName('body')[0]);
<script src=".11.0/umd/react.production.min.js"></script>
<script src=".11.0/umd/react-dom.production.min.js"></script>

I went through several questions on SO regarding default props for functional ponents and they all remend using ES6 default parameters. Here are links to those questions.

  • React - defaultProps vs ES6 default params when destructuring (performances issues)
  • React functional ponent default props vs default parameters

However, when I use that method for writing ponents with effects running on props change, I get unwanted behaviour with non-primitives. For example, the following code will result in an infinite loop.

const Parent = () => {
  let somethingUndefined;

  return (
    <div>
      <Child prop={somethingUndefined} />
    </div>
  );
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

ReactDOM.render(<Parent />, document.getElementsByTagName('body')[0]);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>


I attempted two ways of attempting to circumvent the issue. First, by just assigning a different variable that contains the default, and putting the unmodified prop in the dependency array. ie

const Child = ({ prop }) => {
  const [x, setX] = React.useState(1);

  const defaultedProp = prop || {a: 1};

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);
  // Note we use prop and not defaultedProp here to avoid runnning into the issue above.

  return <div>{x}, {defaultedProp.a}</div>;
};

Another method would be to just use something like (prop || {a:1}) in place of prop everywhere you use it, except in the dependency array. ie

const Child = ({ prop }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {(prop || {a: 1}).a}</div>;
};

But both of these solutions seem suboptimal since it would require a lot of wasted effort (and bulky code).

defaultProps is also a solution to the infinite loop issue but it is deprecated. Note that the example provided in this rfc also uses ES6 default parameters in the code.

Am I missing something? Is there a better way to use default props in stateful functional ponents that run effects on props change?

Share Improve this question edited Feb 3, 2020 at 7:47 Louis Christopher 3685 silver badges18 bronze badges asked Jan 30, 2020 at 11:16 ManavMManavM 3,1082 gold badges22 silver badges33 bronze badges 3
  • Why not pass default props directly from parent itself? – kooskoos Commented Jan 30, 2020 at 11:19
  • @Then that would be the default behaviour for the Parent ponent. We would want the Child ponent to have these defaults (Since a child ponent would most likely be reusable and need the same set of defaults everywhere). – ManavM Commented Jan 30, 2020 at 11:27
  • Long live defaultProps. – KFunk Commented Nov 2, 2020 at 22:51
Add a ment  | 

4 Answers 4

Reset to default 8 +50

I don't know whether this is eligible for an answer but all your concerns could be resolved by declaring your default value as a constant in the app. That means;

const Parent = () => {
  const somethingUndefined;

  return (
    <>
      <Child prop={somethingUndefined} />
    </>
  );
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

You can change the above code to

const Parent = () => {
  const somethingUndefined;

  return (
    <>
      <Child prop={somethingUndefined} />
    </>
  );
};

const defaultPropValue = {a: 1};

const Child = ({ prop = defaultPropValue }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [prop]);

  return <div>{x}, {prop.a}</div>;
};

This will not cause any infinite loops.

The difference bet these two:- In the first, the prop is initialized to a new value ie, {a: 1} and on every state update, this will be a new object (the new object will be in a new memory location), and it invokes the callback again.

In the second, we initialized and assigned the {a: 1} to defaultPropValue which will not change. Then we assigned this defaultPropValue to prop so that on every re-render, the value assigned to the prop will be the same ( or from the same memory location). So it works as expected.

Hope the idea is clear!

The useEffect() will run the first time and invoke the setX() then:

  • setX() will update the state of x which will trigger the ponent to re-render again.
  • prop will receive a new object const Child = ({ prop = {a: 1} }) => {
  • useEffect() will run again and invoke the setX()

the whole process repeats again, This causes an infinite loop.

Instead you could pass a default value to a property and use it in the useEffect() dependencies array

const Parent = () => {
  let somethingUndefined; // babel plains if we use `const` without value

  return (
    <div>
      <Child prop={somethingUndefined} />      
      <Child prop={{ a: 3 }} />
    </div>
  );
};

const Child = ({ prop = {} }) => {
  const { a = 1 } = prop;
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(x + 1);
  }, [a]);

  return <div>{x}, {a}</div>;
};

ReactDOM.render(<Parent />, document.getElementsByTagName('body')[0]);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

See https://codepen.io/McKabue/pen/dyPxGLQ?editors=0010

const Parent = () => {
  const somethingUndefined = undefined;

  return <Child prop={somethingUndefined}/>;
};

const Child = ({ prop = {a: 1} }) => {
  const [x, setX] = React.useState(1);

  React.useEffect(() => {
    setX(prop.a + 1);
  });

  return <div>{x}, {prop.a}</div>;
};


ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
useEffect(() => {
// anything you want to do
, [JSON.stringify(dependencyName)]}

本文标签: javascriptDefault Props in stateful (using hooks) functional componentsStack Overflow