React - 在Ajax返回JSON

时间:2017-11-28 17:21:54

标签: reactjs chart.js

我正在使用我的第一个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} : <div>Loading...</div>}是不正确的,但这是我第一次尝试合并我拥有的两种技术。我的第二次尝试是将{chartToBeDisplayed}替换为通用图表组件(<Chart />),并将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合作,并希望得到一些方向/澄清。谢谢!

1 个答案:

答案 0 :(得分:0)

首次试用时,请尝试在curly brackets删除chartToBeDisplayed

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

只要图表的语法正确,它就应该有效。

进行第二次试验。

Chart.js仅在初始安装时设置状态。

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

尝试在componentWillReceiveProps组件上添加Chart.js

class Chart extends Component {
  constructor(props) {
    ...
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.chartType !== this.state.chartType) {
      this.setState({chartType: nextProps.chartType})
    }
  }
}

更新

为防止误解,上面提到的Chart.js表示有问题的Chart.js文件。