I went through several questions on SO regarding default props for functional components and they all recommend using ES6 default parameters. Here are links to those questions.
However, when I use that method for writing components 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.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/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 components that run effects on props change?
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With