简体   繁体   中英

Why is the child component updating but not re-render Reactjs

I have a main component with a child chart component. On connecting to a websocket, the main component updates the state of the child chart component. This however does not redraw. When I click on the chart however, the labels appear, and when I click again, the values appear with the labels.

Main.js:

import IO from 'socket.io-client';
import React from "react";

import { Switch, Route } from 'react-router-dom';
import { Chart } from "./Chart";

let ftse100Tickers = require('./ftse_100_tickers.json');
let randomInt = Math.floor(Math.random() * ftse100Tickers.tickers.length);

/**
 * Main application component which contains
 */
export class Main extends React.Component {
    componentWillMount() {
        this.socket = IO(location.protocol + "//" + document.domain + ":" + location.port);
        this.socket.on("connect", (() => this.connect()));
        this.socket.on("disconnect", (() => this.disconnect()));
        this.socket.on("initial data", ((data) => this.createInitialChart(data)))
    }

    connect(){
        this.setState({status: 'connected'});
        this.socket.emit("get initial data", this.state.ticker);
    }

    disconnect(){
        this.setState({status: 'disconnected'})
    }

    createInitialChart(data){
        let tempErrorChart= this.state.errorChart;
        for (let row of data){
            tempErrorChart.labels.push(row.time_stamp);
            tempErrorChart.datasets[0].data.push(row.error);
        }
        this.setState({errorChart: tempErrorChart});
    }


    constructor(props){
        super(props);
        this.state = {
            errorChart: {
                labels: [],
                datasets: [
                    {
                        label: 'Error',
                        data: [],
                    },
                ]
            },
            status: 'disconnected',
            ticker : ftse100Tickers.tickers[randomInt],
            twitter : ftse100Tickers.twitter[randomInt]
        }
    }
    render() {
        return (
            <div className="row">
                <div className="row">
                    <div className="col-lg-6">
                        <div className="card border-0">
                            <div className="card-body">
                                <Chart chart={this.state.errorChart}/>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

The chart component is as so:

Chart.js

import { Line } from "react-chartjs-2"
import React from "react";

/*
 * General charting component used for rendering charts
 */
export class Chart extends React.Component {

    render() {
        return (
            <Line data={this.props.chart} options={{}}/>
        )
    }
}

I see one problem and that is you are not changing object references in this.state.errorChart upon errorChart update before you call setState . Even though you push to its properties new items, the object and even the inner array references do not change, and if the Line component does some props checking on whether it should rerender itself or not, it figures by receiving still the same references that nothing has changed and there is no need to rerender.

Now this was just my assumption, but either way it is a good practice to always create new objects while creating new state once those objects are about to be modified. This allows for fast object (state) references comparisons in shouldComponentUpdate methods or while using PureComponent which in turn makes it easier and more performant to determine whether to rerender the component or not. On the other hand, if you would use the same references still, you would have to implement deep comparison of the old and the new state, which is definitely more expensive and very fragile in the long run.

Example on how to correctly update the state follows:

createInitialChart(data) {
  const errorChart = this.state.errorChart

  const newErrorChart = {
    ...errorChart
  }

  newErrorChart.labels = [...errorChart.labels, data.map(row => row.time_stamp)]
  newErrorChart.datasets[0].data = [
    ...errorChart.datasets[0].data,
    data.map(row => row.error)
  ]

  this.setState({ errorChart: newErrorChart })
}

Edit: By looking at the component's shouldComponentUpdate implementation - ChartComponent , It can be clearly seen, that there are multiple options on how to make the Line rerender, eg. by giving redraw={true} prop to the Line component. The procedure above is generally still the safest way to ensure rerender though.

You might need componentWillReceiveProps(nextProps, nextState). You can compare the old state here with the new state and update the state accordingly.

Please set the initialState like so:

constructor(props){
 super(props);
 this.state = {errorChart: {...}}; //your initial values here.
}

then,

componentWillReceiveProps(nextProps, nextState){
 if(this.state.errorChart !== nextState.errorChart){
   let tempErrorChart = {...this.state.errorChart};
    for (let row of data){
        tempErrorChart.labels.push(row.time_stamp);
        tempErrorChart.datasets[0].data.push(row.error);
    }
    this.setState({errorChart: tempErrorChart});
 }
}

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