Reputation: 307
tldr; version: parent not re-rendering after setState called...
I have a dashboard view that renders multiple components. The child components that load data work just fine. But I have another section of the dashboard where I plan to load multiple versions of the same component using the same dataset. Therefore, instead of making 6 api calls for the same dataset, I'm pulling the data in via the parent and sending it down to the children. Also I am using route parameters to signal which company to look up.
The components that contain their own api calls work great, functioning fine with the route params. It's the component where I'm making the api call in the parent where I have the issue. The parent is rendering, then the child, then the api call finishes and sets the state, but isn't reloading. So as the route parameter changes, the component shows the data of the previous click (essentially is always one behind).
I've tried changing which react events to tie it to, I've using mapping functions, made sure that "this" is the "this" that I want (it is)... no dice. I know it's updating the state correctly, and if I forceUpdate() the correct data shows in the component (but I can't do this it would force me to add forceUpdate() into a react update method which will cause it to constantly reload).
Any ideas?? Here's some stripped-down/obfuscated code:
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
Here's what the console looks like. As you can see, no re-render and the "this" is the correct "this":
Upvotes: 2
Views: 6099
Reputation: 2725
The problem lies in your shouldComponentUpdate
. Right now, it only returns true when the props of the new clientID
is different than the current clientID
. So, when you run this.setState()
at the end of your API call, the component does not update because you are not receiving new props.
Basically,
There are two ways to fix this:
shouldComponentUpdate
so that React decides when the component updates (when you run this.setState
or receive new updates)shouldComponentUpdate
that is nextState
, and compare the old and new state to determine when the component will update.Incidentally, I see you have your API call in componentWillMount
. There are some good reasons why you should use componentDidMount
isntead.
Upvotes: 6