How to avoid setState () inside render () when the state depends on the render
I have a grid that displays maps with variable height.
To get the height of the maps, I make the map wrapped in ReactHeight
( https://github.com/nkbt/react-height ), which allows me to pass a callback function to it when the height is ready.
I store the height of the map in an array in a component state
, which I update as I get information about the height of the map.
When all heights are available, I compute the infill from the height that render
goes to the maps
In a simplified version, it looks like this.
class Grid extends Component {
constructor() {
...
this.state = { cardHeights: {}, paddings: [] }; //initial state
}
componentDidUpdate() {
const i = setInterval(() => {
if (all heights are ready) {
// ...calculate paddings
this.setState(paddings: calculatedPaddings);
clearInterval(i);
}
}, 100);
// *A*
}
updateCardHeights(index, height) {
this.setState(prevState => ({
cardHeights: {
...prevState.cardHeights,
[index]: height,
},
}));
}
render() {
// index = cards are indexed like 1 2 3
// 4 5 6
// 7 8 9
// 10 11 12
// ...
return (
<ReactHeight onHeightReady={ height => this.updateCardHeights(index, height) }
<Card padding={this.state.paddings[index]}/>
</ReactHeight>
);
}
}
I know setState()
calls render
, and calling a function that mutates the state internally render
usually results in an infinite loop.
In my code, it render
calls a function that updates the array this.state.cardHeights
, and the rendering of its child component Card
indirectly depends on this.state.cardHeights
, since the one this.state.paddings
it directly depends on is computed based on cardHeights on componentDidUpdate()
.
However, I cannot get the height of the component Card
without rendering it, so the only way I can think of is to render padding
a prop
to the map and state of the component, then change the state after rendering.
My question is:
-
Somehow my code didn't result in an infinite loop, although I change the state inside the render, but the best way to do it?
-
clearInterval()
inside my functioncomponentDidUpdate()
doesn't seem to work. I put a print statement on the element*A*
and it keeps on printing even if the if condition is met andclearInterval()
presumably called. Could this be due to my bad state mutation?
source to share
In such a case, you should check if the previous value and the new value are the same before updating the state. I hope your height won't update for every render. This way you can check updateCardheights
to determine if there is an actual change and then update the state. This ensures that it does not update the state of redundant updates. But if your state is updated on every render, the problem remains the same.
updateCardHeights(index, height) {
if(this.state.cardHeights[index] !== height) {
this.setState(prevState => ({
cardHeights: {
...prevState.cardHeights,
[index]: height,
},
}));
}
}
source to share