【问题标题】:React - render Chart.js chart based on user selection, after Ajax returns JSONReact - 在 Ajax 返回 JSON 后,根据用户选择呈现 Chart.js 图表
【发布时间】:2017-11-28 17:21:53
【问题描述】:

我正在开发我的第一个 React 应用程序,它根据用户在下拉菜单中的当前选择显示一个 Chart.js 图表。当我更改选择时,它可以呈现相应的图表,但它使用的是硬编码数据。从那时起,我添加了一个 Ajax 调用来获取 JSON,但图表组件在 Ajax 响应到来之前呈现。所以,我添加了一个检查以显示“正在加载...”,同时等待 JSON 数据被设置状态。我的问题是我当前渲染正确图表的实现与我在渲染图表之前等待 JSON 数据的实现相冲突。

经过研究,我发现我的问题与这篇文章最相似,但有多个图表可能性而不是一个:React render Chart.js based on api response

这是我的代码:

App.js

import React, { Component } from 'react';
import $ from 'jquery';
import './App.css';
import Header from './Components/Header';
import Dropdown from './Components/Dropdown';
import BarChart from './Components/BarChart';
import DoughnutChart from './Components/DoughnutChart';
import PieChart from './Components/PieChart';
import LineChart from './Components/LineChart';       
import RadarChart from './Components/RadarChart';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      chartData: {}, 
      chartType: 'Bar', //default to bar chart
      isLoaded: false
    };
    this.handleVisualChange = this.handleVisualChange.bind(this);
  }

  componentDidMount() {
    this.getChartData();  
  }

  handleVisualChange(value) {
    //logging for testing
    console.log('testing value in App.js: ', value);
    //updating the state to the current selected visual
    this.setState({chartType: value});
  }

  getChartData() {
    $.ajax({
      url: 'http://localhost/fetch/getData.php',
      dataType: 'json',
      success: function(dataReturned){
        //logging to test if correct data is being received
        console.log('original data: ', dataReturned);

        //loop through array and build array of categories and array of totals
        var arrCAT = [];
        var arrTOTAL = [];
        for (var i = 0, len = dataReturned.length; i < len; i++) {
          arrCAT.push(dataReturned[i].CAT);
          arrTOTAL.push(dataReturned[i].TOTAL);
        }
        //logging to test that arrays were loaded correctly
        console.log('just categories: ', arrCAT);
        console.log('just totals: ', arrTOTAL);

        this.setState({
          chartData: {
            labels: arrCAT,
            datasets: [
              {
                label: '# of Transactions',
                data: arrTOTAL,
                backgroundColor: [
                  'rgba(255, 99, 132, 0.6)',  //red
                  'rgba(54, 162, 235, 0.6)',  //blue
                  'rgba(255, 206, 86, 0.6)',  //yellow
                  'rgba(75, 192, 192, 0.6)',  //green
                  'rgba(153, 102, 255, 0.6)', //purple
                  'rgba(255, 159, 64, 0.6)',  //orange
                  'rgba(90, 96, 104, 0.6)'    //grey
                ]
              }
            ]
          },
          isLoaded: true
        });
      }.bind(this),
    }); 
  }  

  render() {
    let chartToBeDisplayed = null;
    switch(this.state.chartType) {
      case 'Bar':
        chartToBeDisplayed = <BarChart chartData={this.state.chartData} />;
        break;
      case 'Doughnut':
        chartToBeDisplayed = <DoughnutChart chartData={this.state.chartData} />;
        break;
      case 'Pie':
        chartToBeDisplayed = <PieChart chartData={this.state.chartData} />;
        break;
      case 'Line':
        chartToBeDisplayed = <LineChart chartData={this.state.chartData} />;
        break;
      case 'Radar':
        chartToBeDisplayed = <RadarChart chartData={this.state.chartData} />;
        break;
      case 'All':
        chartToBeDisplayed = <div className="all">
                               <BarChart chartData={this.state.chartData} />
                               <DoughnutChart chartData={this.state.chartData} />
                               <PieChart chartData={this.state.chartData} />
                               <LineChart chartData={this.state.chartData} />
                               <RadarChart chartData={this.state.chartData} />
                             </div>;
        break;
      default:
        console.log('Something went wrong...');
    } 
    return (
      <div className="App">
        <Header />
        <Dropdown onVisualChange={this.handleVisualChange} />
        <div className="ChartArea">
          {this.state.isLoaded ? {chartToBeDisplayed} : <div>Loading...</div>}
        </div>
      </div>
    );
  }
}

export default App;

Dropdown.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Dropdown extends Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  static defaultProps = {
    visuals: ['Bar', 'Line', 'Pie', 'Doughnut', 'Radar', 'All']
  }

  handleChange(event) {
    //logging for testing
    console.log('testing value in Dropdown.js: ', event.target.value);
    this.props.onVisualChange(event.target.value);
  }

  render() {
    let visualOptions = this.props.visuals.map(visual => {
      return <option key={visual} value={visual}>{visual}</option>
    });
    return (
      <div className="dropdown">
        <label>Visuals</label><br />
          <select id="soflow" ref="visual" value={this.props.value} onChange={this.handleChange}>
            {visualOptions}
          </select>
      </div>
    );
  }
}

Dropdown.propTypes = {
  onVisualChange: PropTypes.func,
  visuals: PropTypes.array,
  value: PropTypes.string
};

export default Dropdown;

BarChart.js(除图表类型外,其他图表组件相同)

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Bar} from 'react-chartjs-2';

class BarChart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chartData: props.chartData
    }
  }

  static defaultProps = {
    displayTitle: true,
    displayLegend: true,
    legendPosition: 'bottom',
  }

  render() {
    return (
      <div className="chart">
        <Bar
          data={this.state.chartData}
          options={{
            title: {
            display: this.props.displayTitle,
            text: 'Transactions by Category',
            fontSize: 25
            },
            legend: {
            display: this.props.displayLegend,          
            position: this.props.legendPosition 
            }
          }}
        />
      </div>
    )   
  }
}

BarChart.propTypes = {
  chartData: PropTypes.object,
  displayTitle: PropTypes.bool,
  displayLegend: PropTypes.bool,
  legendPosition: PropTypes.string
};

export default BarChart;

当前错误:

Objects are not valid as a React child (found: object with keys {chartToBeDisplayed}). If you meant to render a collection of children, use an array instead.
    in div (at App.js:112)
    in div (at App.js:109)
    in App (at index.js:7)

在 App.js 中,我知道 {this.state.isLoaded ? {chartToBeDisplayed} : &lt;div&gt;Loading...&lt;/div&gt;} 不正确,但这是我第一次尝试合并我拥有的两种技术。我的第二次尝试是用通用图表组件 (&lt;Chart /&gt;) 替换 {chartToBeDisplayed},并将 switch(this.state.chartType) 放在该组件的 render() 内,如下所示:

Chart.js

import React, {Component} from 'react';
import BarChart from './BarChart';
import DoughnutChart from './DoughnutChart';
import PieChart from './PieChart';
import LineChart from './LineChart';
import RadarChart from './RadarChart';
import PropTypes from 'prop-types';
import {Bar, Line, Pie, Doughnut, Radar} from 'react-chartjs-2';

class Chart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chartData: props.chartData,
      chartType: props.chartType
    }
  }

  render() {
    let chartToBeDisplayed = null;
    switch(this.state.chartType) {
      case 'Bar':
        chartToBeDisplayed = <BarChart chartData={this.state.chartData} />;
        break;
      case 'Doughnut':
        chartToBeDisplayed = <DoughnutChart chartData={this.state.chartData} />;
        break;
      case 'Pie':
        chartToBeDisplayed = <PieChart chartData={this.state.chartData} />;
        break;
      case 'Line':
        chartToBeDisplayed = <LineChart chartData={this.state.chartData} />;
        break;
      case 'Radar':
        chartToBeDisplayed = <RadarChart chartData={this.state.chartData} />;
        break;
      case 'All':
        chartToBeDisplayed = <div className="all">
                               <BarChart chartData={this.state.chartData} />
                               <DoughnutChart chartData={this.state.chartData} />
                               <PieChart chartData={this.state.chartData} />
                               <LineChart chartData={this.state.chartData} />
                               <RadarChart chartData={this.state.chartData} />
                             </div>;
        break;
      default:
        console.log('Something went wrong...');
    } 
    return (
      <div className="chart">
        {chartToBeDisplayed}
      </div>
    );  
  }
}

Chart.propTypes = {
  chartData: PropTypes.object,
  chartType: PropTypes.string,
};

export default Chart;

通过这个实现,它会将 App.js 更改为:

import React, { Component } from 'react';
import $ from 'jquery';
import './App.css';
import Header from './Components/Header';
import Dropdown from './Components/Dropdown';
import Chart from './Components/Chart';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      chartData: {}, 
      chartType: 'Bar',
      isLoaded: false
    };
    this.handleVisualChange = this.handleVisualChange.bind(this);
  }

  componentDidMount() {
    this.getChartData();  
  }

  handleVisualChange(userSelection) {
    //logging for testing
    console.log('testing value in App.js: ', userSelection);
    //updating the state to the current selected visual
    this.setState({chartType: userSelection});
  }

  getChartData() {
    $.ajax({
      url: 'http://localhost/test/fetch.php',
      dataType: 'json',
      success: function(dataReturned){
        //logging to test if correct data is being received
        console.log('original data: ', dataReturned);

        //loop through array and build array of categories and array of totals
        var arrCAT = [];
        var arrTOTAL = [];
        for (var i = 0, len = dataReturned.length; i < len; i++) {
          arrCAT.push(dataReturned[i].CAT);
          arrTOTAL.push(dataReturned[i].TOTAL);
        }
        //logging to test that arrays were loaded correctly
        console.log('just categories: ', arrCAT);
        console.log('just totals: ', arrTOTAL);

        this.setState({
          chartData: {
            labels: arrCAT,
            datasets: [
              {
                label: '# of Transactions',
                data: arrTOTAL,
                backgroundColor: [
                  'rgba(255, 99, 132, 0.6)',  //red
                  'rgba(54, 162, 235, 0.6)',  //blue
                  'rgba(255, 206, 86, 0.6)',  //yellow
                  'rgba(75, 192, 192, 0.6)',  //green
                  'rgba(153, 102, 255, 0.6)', //purple
                  'rgba(255, 159, 64, 0.6)',  //orange
                  'rgba(90, 96, 104, 0.6)'    //grey
                ]
              }
            ]
          },
          isLoaded: true
        });
      }.bind(this),
    }); 
  }  

  render() {
    return (
      <div className="App">
        <Header />
        <Dropdown onVisualChange={this.handleVisualChange} />
        <div className="ChartArea">
          {this.state.isLoaded ? <Chart chartData={this.state.chartData} chartType={this.state.chartType} /> : <div>Loading...</div>}
        </div>
      </div>
    );
  }
}

export default App;

但是,我也无法使其正常工作。当我更改下拉列表中的选择时,React Developer Tools 允许我看到状态正在正确更改,但它没有呈现新的图表选择。它只是保持默认值,即 BarChart。

底线:我不确定我是否接近实现这一目标,但缺少一些小细节,或者我是否需要对如何呈现正确图表进行重大更改? switch 语句的实现感觉不对,但它本身运行得非常好。正如我所说,我是使用 React 的新手,希望得到一些指导/澄清。谢谢!

【问题讨论】:

    标签: reactjs chart.js


    【解决方案1】:

    对于您的第一次试用,请尝试在chartToBeDisplayed 上删除curly brackets

    {this.state.isLoaded ? chartToBeDisplayed : <div>Loading...</div>}
    

    只要你的图表语法正确,它应该可以工作。


    为您的第二次试用。

    Chart.js 仅在初始挂载时设置状态。

    因此,当您更新 App.js 的状态并将新的 props - chartType 传递给 Chart.js 时,您需要 update the local state

    尝试在Chart.js 处添加componentWillReceiveProps 组件。

    class Chart extends Component {
      constructor(props) {
        ...
      }
    
      componentWillReceiveProps(nextProps) {
        if (nextProps.chartType !== this.state.chartType) {
          this.setState({chartType: nextProps.chartType})
        }
      }
    }
    

    更新

    为防止误解,上面提到的Chart.js是指问题上发布的Chart.js文件。

    【讨论】:

    • 我已经实现了这两种解决方案,它们都运行良好。但是,您对哪种实施方式更好有建议吗?我打算继续将此应用程序开发成仪表板,以便它的大小和复杂性不断增长。我倾向于第二个实现,但想听听你的想法@Chen-Tai Hou
    • 对我来说,第二种方法更好,因为您可以保持图表 api 相同。它似乎更容易维护。
    猜你喜欢
    • 1970-01-01
    • 2017-06-02
    • 1970-01-01
    • 2021-04-25
    • 2018-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-08
    相关资源
    最近更新 更多