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.
- React - defaultProps vs ES6 default params when destructuring (performances issues)
- React functional component default props vs default parameters
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?