'What is the status of the 'default props rerender' trap in React?
I'm looking for a canonical reference to how this has been dealt with.
If I have a component that looks like this:
const MyComponent = ({ value = [] }) => {
const [otherValue, setOtherValue] = React.useState([]);
React.useEffect(() => {
setOtherValue(value);
}, [value]);
return <div> doesn't matter</div>
};
What happens is:
- When
valuechanges, the useEffect callback fires, and this calls a setState - The setState causes a rerender, if
valueis nullish, then the default array is assigned, and this is a new object, and so this in turn, causes a rerender. - Infinite loop.
The solution can be to declare your default prop as a constant, like:
const DEFAULT_VALUE = [];
const MyComponent = ({ value = DEFAULT_VALUE }) => {
const [otherValue, setOtherValue] = React.useState([]);
My questions:
For each of the version 16, 17, 18 of React:
- Is this an issue that has been solved? If, so which version solved it?
- If it hasn't been solved, a pointer to the 'won't solve' decision.
- Is there an eslint rule to capture this?
- Anything else that is generally helpful in avoiding this trap.
Solution 1:[1]
You can use a property called defaultProps. With defaultProps the default value of your property become part of the definition the component and is no longer reseted at every render, preventing the infinity loop with useEffect. So it will be something like that:
const MyComponent = ({ value }) => {
const [otherValue, setOtherValue] = React.useState([]);
React.useEffect(() => {
setOtherValue(value);
}, [value]);
return <div> doesn't matter</div>
};
MyComponent.defaultProps = {
value: []
};
And yes, there is an lint rule for that, but the rule is:
Enforce a defaultProps definition for every prop that is not a required prop
So it is not exactly what you want because it works with prop-types rules but I think will help you with it. If you wanna see more details about this rule here is the readme describing it.
Solution 2:[2]
I haven't tested this, but why you dont add a validation to test if the array exist and in that case set the internal component state?
Use of defaultProps will be deprecated soon:
https://twitter.com/dan_abramov/status/1133878326358171650
const MyComponent = ({ value = [] }) => {
const [otherValue, setOtherValue] = React.useState([]);
React.useEffect(() => {
if (value !== null) setOtherValue(value); // could be: value?.length > 0
}, [value]);
return <div> doesn't matter</div>
};
One other way I think about is using de || operator:
const MyComponent = (props) => {
const value = props.value || [];
...
};
Solution 3:[3]
The infinite loop is not related to ReactJS, so it couldn't be fixed by any versions. the issue returns to primitive/reference values.
When you are using this:
const MyComponent = ({ value = [] }) => {
const [otherValue, setOtherValue] = React.useState([]);
React.useEffect(() => {
setOtherValue(value);
}, [value]);
For even one change from props, if the next value is undefined javascript create a new value [] and assign it to value and React.useEffect thinks it's new (actually this thought is correct) and tries to get the new prop and compare with the last one and for any of these actions use value and each time it is new because its reference is new.
But your solution is awesome, for any reference type values we must cache it and then use it to avoid creating it over and over:
const DEFAULT_VALUE = [];
const MyComponent = ({ value = DEFAULT_VALUE }) => {
ESLint:
There are some plugin rules to force developers to add default values in required or not-required props, but none of them hints you to cache the reference-type values for default props.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | AmerllicA |
