I'm testing an app that toggles a chart between two data objects: chart1 and chart2. I have an App component which handles the rendering of a Bar chart and a Button. When the Button is clicked I call a function in App to update the state to reference either chart1 or chart2 depending on a boolean.
This works successfully the first time I click the button. The state changes in App and it re-renders the Bar component with the new data. However, if I click the button again the state doesn't change and the chart doesn't update. Only the initial button click does anything. I've tested without react-chartjs-2 using plain components and everything works fine - each button click changes the data.
Am I using the Bar component wrong? I'm passing the state of App as its props and it works fine initially, then stops updating after the first button click.
import React from 'react';
import ReactDOM from 'react-dom';
import { Bar } from 'react-chartjs-2';
const chart1 = {
labels: ['Team1', 'Team2', 'Team3', 'Team4', 'Team5'],
datasets: [{
label: 'Team points',
data: [503, 385, 270, 133, 65],
backgroundColor: [
'#4DB6AC',
'#E57373',
'#7986CB',
'#F06292',
'#E0E0E0'
]
}]
};
const chart2 = {
labels: ['Team1', 'Team2', 'Team3', 'Team4', 'Team5'],
datasets: [{
label: 'Team points 2',
data: [303, 185, 470, 313, 65],
backgroundColor: [
'#4DB6AC',
'#E57373',
'#7986CB',
'#F06292',
'#E0E0E0'
]
}]
};
const Button = (props) => (
<button id="update-chart" onClick={props.handleOnClick}>Update</button>
);
class App extends React.Component {
constructor(props) {
super(props);
this.handleUpdate = this.handleUpdate.bind(this);
this.updated = false;
this.state = {
chartData: chart1
}
console.log(this.state.chartData);
console.log(this.updated);
}
// Toggle between chart1 and chart2 based on value of updated
handleUpdate() {
const chartData = this.updated ? chart1 : chart2;
this.setState({chartData}, () => {
this.updated = this.updated ? false : true;
console.log(this.state.chartData);
});
}
render() {
return(
<div>
<Bar data={this.state.chartData} width={650} height={400} />
<Button handleOnClick={this.handleUpdate} />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('chart-container')
);
Hey haven't figured this out, but noticed that when I log chart1 and chart2 at the top of handleUpdate, after the first click, they are the same data.
It does work if you move the chart1 and chart2 data down as properties of the App and reference them as this.chart1 and this.chart2.
Okay, so you can keep the chart1 and chart2 where they are and keep the handleUpdate the same. Make chart1 and chart2 anonymous functions that return the data object, like this:
const chart1 = () => {
return {
labels: ["Team1", "Team2", "Team3", "Team4", "Team5"],
datasets: [
{
label: "Team points",
data: [503, 385, 270, 133, 65],
backgroundColor: ["#4DB6AC", "#E57373", "#7986CB", "#F06292", "#E0E0E0"]
}
]
};
};
const chart2 = () => {
return {
labels: ["Team1", "Team2", "Team3", "Team4", "Team5"],
datasets: [
{
label: "Team points 2",
data: [303, 185, 470, 313, 65],
backgroundColor: ["#4DB6AC", "#E57373", "#7986CB", "#F06292", "#E0E0E0"]
}
]
};
};
Yep that works!
Any idea why my initial code doesn't work? Aren't those functions just returning objects which are the same as how I initially defined them? In reality I'd probably be pulling the data from a function anyway, those object were just for testing, but I'm not sure why the plain object notation didn't work.
Not sure, for some reason the data is getting out of whack! May work using setState this way: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous.
I did try the function argument of setState before but no dice.
Interestingly, if I remove react-chartjs-2 from the equation and make my Bar a plain function component that renders a div, everything works fine even without using the anonymous functions. So it appears I was conflicting with the react-chartjs-2 component somehow.
use componentWillReceiveProps(props) and set state inside. It works for me
@SmCTwelve I think your problem is setting this.updated directly on the constructor of App. Rather, set it as a property of state, and read from and update it using this.setState(). My understanding of React is that, if you're updating this.update directly on the constructor, you're not signaling to React to rerender your chart component - it isn't receiving new props or responding to updated state. Why it works the first time you click, I'm not sure.
Sidenote: You don't need an ID on your button
import React from 'react';
import ReactDOM from 'react-dom';
import { Bar } from 'react-chartjs-2';
const chart1 = {
labels: ['Team1', 'Team2', 'Team3', 'Team4', 'Team5'],
datasets: [{
label: 'Team points',
data: [503, 385, 270, 133, 65],
backgroundColor: [
'#4DB6AC',
'#E57373',
'#7986CB',
'#F06292',
'#E0E0E0'
]
}]
};
const chart2 = {
labels: ['Team1', 'Team2', 'Team3', 'Team4', 'Team5'],
datasets: [{
label: 'Team points 2',
data: [303, 185, 470, 313, 65],
backgroundColor: [
'#4DB6AC',
'#E57373',
'#7986CB',
'#F06292',
'#E0E0E0'
]
}]
};
const Button = (props) => (
<button id="update-chart" onClick={props.handleOnClick}>Update</button>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
chartData: chart1,
updated: false
}
this.handleUpdate = this.handleUpdate.bind(this);
}
// Toggle between chart1 and chart2 based on value of updated
handleUpdate() {
const chartData = this.state.updated ? chart1 : chart2;
// Batching both updates to state in the same call to this.setState
this.setState({chartData, updated: !this.state.updated});
// for updated, read from what is currently set as updated in state and do the opposite - creating a toggle
}
render() {
return(
<div>
<Bar data={this.state.chartData} width={650} height={400} />
<Button handleOnClick={this.handleUpdate} />
</div>
);
}
}
I have the same issue and my state change but the chart just update with the first click.
Closing for now. Please supply a sandbox or code example with the latest versions of both libraries. If the problem persists, feel free to open a new issue.
Encountered exactly same problem (firstly stored chart data and options objects in variables), then according to @graysonhicks comment, I moved chart data and options objects into anonymous functions that return them as data objects and everything works fine.
I had exactly the same issue. Initial data appears, I click a button to toggle a different view (which updates data object) updated data appears. However when attempting to untoggle to view initial data the graph does not update.
I wasn't able to fix it using anonymous functions, however, it seems using the redraw option functionally fixed things for me - albeit not as smooth as I'd like.
<Line redraw={true} data={data} />
I have noticed a 'bobbing' effect on the first untoggle but otherwise works as expected.
Most helpful comment
I have the same issue and my state change but the chart just update with the first click.