I have a grid that renders cards of variable heights.
To get the height of the cards, I render the card wrapped in ReactHeight
(https://github.com/nkbt/react-height), which lets me pass a callback function to it when the height is ready.
I keep the card heights in an array in the component state
, which I update as card heights get reported.
When all heights are available, I calculate the padding from the heights, which render
passes to the cards
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 mutating the state inside render
generally leads to an infinite loop.
In my code, render
calls a function that updates this.state.cardHeights
array, and the rendering of its child component, Card
, indirectly depends on this.state.cardHeights
, since this.state.paddings
that it directly depends on is calculated based on cardHeights on componentDidUpdate()
.
However, I cannot get the height of the Card
component without rendering it, so the only way I can think of is making padding
a prop
to card and state of the component, and then changing the state after rendering.
My question is:
Somehow my code didn't result in an infinite loop even though I'm changing state inside render, but is a better way to do this?
clearInterval()
inside my componentDidUpdate()
function doesn't seem to work. I put a print statement in *A*
part, and it keeps printing even when the if condition is met and clearInterval()
is supposedly called. Can this be because of my bad state mutation?
Because setState() function changes the state of the application and causing a change in the state called the render() function again. So if you write something like this then calling the function stack will go for infinity and application gets the crash.
The render() function should be pure, meaning that it does not modify a component's state. It returns the same result each time it's invoked, and it does not directly interact with the browser. In this case, avoid using setState() here.
The most straightforward approach to preventing state update re-renders is by avoiding the state update entirely. This means making sure that the setState function is not called unless strictly necessary.
In other words, if we update state with plain JavaScript and not setState , it will not trigger a re-render and React will not display those (invalid) changes in state to our user. This is a simple, but crucial lesson to remember.
In such case you should check if the previous value and the new value are same before updating the state. I hope your height does not gets updated for each render. So you can have a check inupdateCardheights
to determine if there is an actual change and then update the state. This ensures that it does not updates the state for redundant updates. But if on each render your state is going to be updated, then the problem would remain the same.
updateCardHeights(index, height) {
if(this.state.cardHeights[index] !== height) {
this.setState(prevState => ({
cardHeights: {
...prevState.cardHeights,
[index]: height,
},
}));
}
}
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