【问题标题】:React component not re-rendering on setStateReact 组件未在 setState 上重新渲染
【发布时间】:2017-06-30 16:26:17
【问题描述】:

tldr;版本:调用 setState 后父级未重新渲染...

我有一个显示多个组件的仪表板视图。加载数据的子组件工作得很好。但是我有仪表板的另一部分,我计划使用相同的数据集加载同一组件的多个版本。因此,我不是对同一个数据集进行 6 次 api 调用,而是通过父级提取数据并将其发送给子级。我还使用路由参数来指示要查找的公司。

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

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

有什么想法吗??这是一些精简/混淆的代码:

家长

...
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)

儿童

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

这是控制台的外观。如您所见,没有重新渲染,“this”是正确的“this”:

【问题讨论】:

  • 如果父母不更新,孩子也不会。您必须相应地更新您的shouldComponentUpdate(它接受第二个参数nextState
  • @TylerSebastian 是对的。如果您通过控制台登录shouldComponentUpdate,它应该会出现。它已经停在那里了。
  • 感谢您的帮助,您说的都是对的。我刚刚在shouldComponentUpdate 方法的返回值中添加了一个!(_.isEqual(nextState, this.state))

标签: reactjs axios setstate


【解决方案1】:

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

基本上,

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

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

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

顺便说一句,我看到您在componentWillMount 中有您的 API 调用。 There are some good reasons why you should use componentDidMount isntead.

【讨论】:

    猜你喜欢
    • 2019-08-10
    • 2020-01-22
    • 2021-04-28
    • 2015-06-30
    • 1970-01-01
    • 1970-01-01
    • 2019-09-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多