reactchartjs / react-chartjs-2

React components for Chart.js, the most popular charting library
https://react-chartjs-2.js.org
MIT License
6.58k stars 2.37k forks source link

Chart won't update with state change #205

Closed SmCTwelve closed 4 years ago

SmCTwelve commented 6 years ago

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')
);
graysonhicks commented 6 years ago

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.

graysonhicks commented 6 years ago

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"]
            }
        ]
    };
};
SmCTwelve commented 6 years ago

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.

graysonhicks commented 6 years ago

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.

SmCTwelve commented 6 years ago

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.

https://jsfiddle.net/38rr1udt/3/

ttibau commented 6 years ago

use componentWillReceiveProps(props) and set state inside. It works for me

dbrodie122 commented 6 years ago

@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>
    );
  }
}
valentinorusconi commented 6 years ago

I have the same issue and my state change but the chart just update with the first click.

Pringels commented 4 years ago

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.

ebasic commented 4 years ago

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.

OffBy0x01 commented 3 years ago

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.

mariomendonca commented 2 years ago

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.

man tks a lot, you saved my life