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 function componentDidUpdate()

    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 and clearInterval()

    presumably called. Could this be due to my bad state mutation?

+3


source to share


2 answers


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,
   },
  }));
 }
}

      

+2


source


I have the following solution to your problem above. Just assign your event handler outside of the render function. And don't forget to bind your event handler inside the constructor. After that you can call this event handler inside your render function



0


source







All Articles