简体   繁体   中英

How to get state to update based on an update to props?

I have a component that looks like:


class Table extends Component {
  constructor(props) {
    super(props);

    this.columnDefs = columnDefs;
    this.state = {
      isLoading: true,
      showTimeoutModal: false,
      tableData: [],
      currentDate: null
    };
  }

  componentDidMount() {
    this.fetchData();
  }

  fetchData() {
    this.setState({
      isLoading: true,
      currentDate: this.props.currentDate
    });

    someApi.getData(this.props.currentDate).then(tableData => this.setState({
      tableData: tableData,
      isLoading: false
    }))
      .catch(error => {
        if (error.message === 'timeout') {
          this.setState({
            showTimeoutModal: true
          });
        } else {
          throw new Error(error.message);
        }
      });
  }

  onGridReady(params) {
    this.gridApi = params.api;
    this.gridApi.sizeColumnsToFit();
  }

  renderGrid() {
    const { tableData, isLoading, showTimeoutModal } = this.state;
    let currentView = null;
    console.log(this.props.currentDate);
    console.log(this.state.currentDate);
    if (!isLoading) {
      if (tableData) {
        currentView =
          <div>
            <AgGridReact
              columnDefs={this.columnDefs}
              rowData={tableData}
              pagination
              paginationPageSize={25}
              headerHeight="43"
              onGridReady={params => this.onGridReady(params)}
              rowSelection="single"
              animateRows="true" />
          </div>;
      } else {
        currentView = <h6>There is no job data to display!</h6>;
      }
    } else if (showTimeoutModal) {
      currentView = <div>
        <TimeoutModalComponent />
        <h6>Timed out</h6>
      </div>;

    } else {
      currentView = <LoadingComponent />;
    }

    return (
      <div>

        {currentView}
      </div>
    );
  }

  render() {
    return (
      <div>
        {this.renderGrid()}
      </div>
    );
  }

}

export default Table;

It gets the currentDate property from a datepicker component that looks like this:


class DatePicker extends Component {
  constructor(props) {
    super(props);
    this.onDateChange = this.onDateChange.bind(this);
    this.state = {
      currentDate: moment(),
      focused: false
    };
  }

onDateChange() {
    this.setState({
      currentDate: this.props.currentDate
    });
  }

  render() {
    return (
      <div className="form-group">


        <SingleDatePicker
          date={this.state.currentDate}
          onDateChange={this.onDateChange}
          focused={this.state.focused}
          onFocusChange={({ focused }) => this.setState({ focused: focused })}
        />
        <TableComponent currentDate={`${this.state.currentDate}`} onDateChange={this.onDateChange} />
      </div>

    );
  }
}

export default DatePicker;

When I select a new date from the date picker, this.props.currentDate on the child component gets updated. This is the date that I want. However when that prop is updated, it doesn't re-render the table with the new expected data. I realize I have to update the state of the child to get the table to re-render. I am trying to do this by setting the state in the fetchData() method by setting currentDate: this.props.currentDate. But this does not work, it doesn't update the state in real time, therefore the grid doesn't re-render. I think I am missing something conceptually about react but I am not sure what. I think the datePicker component is fine, since it can send the correct date when a date is selected and this date can be passed to the table component. Can anyone tell me why this state won't update? Or better yet, what is the best way to handle the state change when this.props.currentDate updates?

EDIT: I am now able to get the state to change with

componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.currentDate !== this.state.currentDate) {
  this.setState({currentDate: nextProps.currentDate});
}

}

but it still won't re-render the table on update.

Ok so you have multiple issues that I can see:

  • You are managing the state of currentDate in both your DatePicker and Table components, this is a big red flag, a big NO NO, and you need to better understand " Lifting State Up "
  • Another issue is <TableComponent currentDate={ ${this.state.currentDate} } . Now, off the top of my head, I do not know if this is actually causing your Table to NOT re-render but it is still not a good binding in a general software engineering sense. You should not be converting currentDate to a string, you loose the ref to currentDate that React state is managing for you, and a ref change is a sure fire way to trigger a re-render with any frontend data binding lib (Angular, Knockout, ReactJs, Vue.js) The component that displays currentDate can internally be responsible for string conversion to display in HTML.

You want to put your Table component and DatePicker component in a parent component that controls the layout for both the Table component and the DatePicker component and also manages the state shared between these two components. This change is going to completely change you approach and free up your mind to better manage your ReactJs app. This is " Lifting State Up ". You will find that your re-render issue mostly likely doesn't even existing after my suggested refactoring.

Use componentDidUpdate(prevProps) to capture props change event in both Table and DatePicker components.

Avoid using componentWillReceiveProps.

Using this lifecycle method often leads to bugs and inconsistencies as per react.org

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM