Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to not use setState inside render function in React

I have a complete running code, but it have a flaw. It is calling setState() from inside a render(). So, react throws the anti-pattern warning.

Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount

My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.

import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';

const initialval = '8998998998';

class Dist extends Component {
  constructor() {
    this.state = {
      checkData: true,
      theData: ''
    };
    this.graphs(initialval);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.cost !== nextProps.cost) {
      this.setState({
        checkData: true
      });
    }
  }

  graphs(val) {
    //Calls a redux action creator and goes through the redux process
    this.props.init(val);
  }

  render() {
    if (this.props.cost.length && this.state.checkData) {
      const tmp = this.props.cost;
      //some calculations
      ....
      ....
      this.setState({
        theData: tmp,
        checkData: false
      });
    }

    return (
      <div>
        <FormComponent onGpChange={recData => this.graphs(recData)} />
        <PieGraph theData={this.state.theData} />
      </div>
    );
  }
}

The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.

handleFormSubmit = (e) => {
    this.props.onGpChange(this.state.value);
    e.preventdefaults();
}

The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?

like image 527
nabs82 Avatar asked Aug 03 '18 17:08

nabs82


Video Answer


1 Answers

Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.

checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.

Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.

import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';

const initialval = '8998998998';

class Dist extends Component {
  constructor() {
    this.state = {
      theData: ''
    };
    this.graphs(initialval);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.cost != nextProps.cost) {
      this.setState({
        theData: this.props.cost
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState){
     if(nextProps.cost !== this.props.cost){
         return true;
     }
     return false;
  }
  graphs(val) {
    //Calls a redux action creator and goes through the redux process
    this.props.init(val);
  }

  render() {
    return (
      <div>
        <FormComponent onGpChange={recData => this.graphs(recData)} />
        {this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
      </div>
    );
  }
}

PS:- The above solution is for version React v15.

like image 139
Hemadri Dasari Avatar answered Sep 26 '22 05:09

Hemadri Dasari