在 setState 上反应组件不是 re-rendering

React component not re-rendering on setState

tldr;版本:parent 不是 re-rendering 在调用 setState 之后...

我有一个呈现多个组件的仪表板视图。加载数据的 child 组件工作正常。但是我有仪表板的另一部分,我计划在其中使用相同的数据集加载相同组件的多个版本。因此,我不是对同一个数据集调用 6 api,而是通过 parent 提取数据并将其发送到 children。我还使用路由参数来指示要查找的公司。

包含自己的 api 调用的组件运行良好,与路由参数一起运行良好。这是我在出现问题的 parent 中进行 api 调用的组件。 parent 正在呈现,然后是 child,然后 api 调用完成并设置状态,但没有重新加载。所以随着路由参数的变化,组件显示的是上一次点击的数据(本质上总是落后一个)。

我已经尝试更改将其绑定到哪些反应事件,我已经使用映射函数,确保“this”是我想要的“this”(它是)......没有骰子。我知道它正在正确更新状态,如果我 forceUpdate() 正确的数据显示在组件中(但我不能这样做,它会迫使我将 forceUpdate() 添加到反应更新方法中,这将导致它不断重新加载).

有什么想法吗??这是一些 stripped-down/obfuscated 代码:

Parent

...
class ClientDash extends Component {
    constructor(props) {
        super(props);
        this.state = { chartOptions: {}, data1: {}, data2: {}, data3: {}, data4: {}, data5: {}, data6: {}, loaded: false };
    }

    loadData(clientID) {
        var currYear = moment().year();
        var chartData = {
            labels: [(currYear-4).toString(), (currYear-3).toString(), (currYear-2).toString(), (currYear-1).toString(), currYear.toString()],
            datasets: [{
                type: 'bar',
                label: 'Cat1',
                backgroundColor: this.props.colors[0].borderColor,
                borderColor: 'white',
                borderWidth: 3,
                data: [null, null, null, null, null]
            }, {
                type: 'bar',
                label: 'Cat2',
                backgroundColor: this.props.colors[1].borderColor,
                borderColor: 'white',
                borderWidth: 3,
                data: [null, null, null, null, null]
            }, {
                type: 'bar',
                label: 'Cat3',
                backgroundColor: this.props.colors[2].borderColor,
                borderColor: 'white',
                borderWidth: 3,
                data: [null, null, null, null, null]
            }, {
                type: 'bar',
                label: 'Cat4',
                backgroundColor: this.props.colors[3].borderColor,
                borderColor: 'white',
                borderWidth: 3,
                data: [null, null, null, null, null]
            }]
        };

        var chartOptions = {
            tooltips: {
                mode: 'index',
                intersect: true
            },
            responsive: true
        };

        
        axios(apiCall)
            .then(d => {
                var data = d.data;
                var data1 = _.clone(chartData),
                    data2 = _.clone(chartData),
                    data3 = _.clone(chartData),
                    data4 = _.clone(chartData),
                    data5 = _.clone(chartData),
                    data6 = _.clone(chartData);

                
                /* some data manipulation */

                console.log('loading data');
                console.log(this);
                this.setState({ chartOptions: chartOptions, data1: data1, data2: data2, data3: data3, data4: data4, data5: data5, data6: data6, loaded: true });
                // this.forceUpdate();
            }).then()
            .catch(function (error) {
                console.log(error);
            })
    }

    componentWillUpdate(nextProps) {
        this.initComp(nextProps.params.clientID);
    }

    shouldComponentUpdate(nextProps) {
        return nextProps.params.clientID != this.props.params.clientID;
    }

    componentWillMount() {
        this.initComp(this.props.params.clientID);
    }

    render() {
        console.log('parent rendering')
// removed all unnecessary code below
        return (
            <div className="wrapper wrapper-content animated fadeIn">
                <div className="row">
                    <div className="col-lg-4">
                        <div className="ibox">
                            <div className="ibox-content">
                                <MetricsColumnChart metricsData={this.state.data1} options={this.state.chartOptions} />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }

}

/**
 * Map the state to props.
 */
const mapStateToProps = (state) => ({
  ...state
});

/**
 * Map the actions to props.
 */
const mapDispatchToProps = (dispatch) => {
    return bindActionCreators(Actions, dispatch)
};


/**
 * Connect the component to
 * the Redux store.
 */
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ClientDash)

Child

import React, { Component } from 'react';
import {Bar} from 'react-chartjs-2';
var _ = require('lodash');

class MetricsColumnChart extends Component {
    render() {
        console.log('child rendering')
        return(
            <Bar data={this.props.metricsData} options={this.props.options} />
        )
    }
}

export default MetricsColumnChart

这是控制台的样子。如您所见,没有 re-render 并且“this”是正确的“this”:

问题出在你的shouldComponentUpdate。现在,只有当新 clientID 的 props 与当前 clientID 不同时,它才 returns 为真。因此,当您 运行 this.setState() 在 API 调用结束时,组件不会更新,因为 您没有收到新的道具

基本上,

  1. 收到初始道具
  2. componentWillMount 运行s
  3. 开始 API 调用(异步)
  4. componentWillMount 完成
  5. 初始渲染
  6. 结束API通话
  7. 设置状态(this.setState)
  8. 应该更新组件吗?没有新道具,因此没有更新
  9. 获得新道具
  10. 应该更新组件吗?是的新道具,因此是更新
  11. componentWillUpdate 运行s
  12. API 调用开始(异步)
  13. componentWillUpdate 完成
  14. 新渲染(使用当前可用状态)
  15. API 调用完成
  16. 设置新状态
  17. 应该更新组件吗?没有新道具,因此没有更新

有两种方法可以解决这个问题:

  1. 删除 shouldComponentUpdate 以便 React 决定何时更新组件(当您 运行 this.setState 或收到新更新时)
  2. 跟随,并向shouldComponentUpdate添加第二个参数,即nextState,并比较新旧状态以确定组件何时更新。

顺便说一句,我看到你在 componentWillMount 有你的 API 电话。