首页 > 解决方案 > React Chart.js 将 API 数据与标题显示的内容匹配,然后根据数据/标题更改更新图表

问题描述

我有一个 Title.js,它从 API 调用中提取标题,从那里我将它发送到 TitleCycle.js 以循环通过来自 API 调用的每个作业,在这个文件中,我还有我的记录data[index]['3'].valueID在职位名称下方显示以供参考(见图)此编号将所有记录与职位名称以及周期相关联。我已经更新了我的代码以显示现在我正在循环通过 API 调用中的任何 api 数据,从这里我不知道如何{jobId}从我的 api 调用正文中的 TitleCycle.js 调用以仅获取什么记录标题更改时与记录 ID 匹配。然后更新标题的更改。

现在,我可以输入一个数字并使用该数据准确更新折线图。但是,我不想有一个数字,而是想从 TitleCycle.js 中提取它来显示当前正在显示的任何 Title 的 Record ID,因此它会随之循环。

折线图.js

import React, { useState, useEffect } from "react";
import { Scatter } from "react-chartjs-2";
import jobId from '../TitleCycle';
// import Title from '../header/Title';

const TotalLineChart = () => {
  const [chartData, setChartData] = useState({});

  const chart = () => {
    let designHours = [];
    let designAmount = [];
    let subRoughHours = [];
    let subRoughAmount = [];
    let roughHours = [];
    let roughAmount = [];
    let finishHours = [];
    let finishAmount = [];
    let closeHours = [];
    let closeAmount = [];
    let actualHours = [];
    let actualAmount = [];

    let headers = {
      "QB-Realm-Hostname": "XXXXXXXXXX.quickbase.com",
      "User-Agent": "FileService_Integration_V2.1",
      "Authorization": "QB-USER-TOKEN XXXXXXXXXXXXXX",
      "Content-Type": "application/json"
    };

    let body = {
      from: "bpz99ram7",
      select: [
        3,
        6,
        80,
        81,
        82,
        83,
        86,
        84,
        88,
        89,
        90,
        91,
        92,
        93,
        94,
        95,
        96,
        97,
        98,
        99,
        101,
        103,
        104,
        105,
        106,
        107,
        109,
        111,
        113,
        115,
        120,
        123,
        224,
        225,
        226,
        227,
        228,
        229,
        230,
        231,
        477,
        479,
        480,
        481
      ],
      where: "{3.EX. '290'}", // this is where i'm attempting to place {jobId}, right now it is set to 290 manually, thats where the data is coming from in the images below. ONLY EFFECTING TOP LINE CHART!
      sortBy: [{ fieldId: 6, order: "ASC" }],
      groupBy: [{ fieldId: 40, grouping: "equal-values" }],
      options: { skip: 0, compareWithAppLocalTime: false }
    };

    fetch("https://api.quickbase.com/v1/records/query", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(body)
    }) 
      .then((response) => response.json())
      .then((res) => {
        console.log(res);
        console.log({jobId})
        Object.keys(res.data).map(jobId => {
          const designHours = parseInt(res.data[jobId]['88'].value, 10);
          const designAmount = parseInt(res.data[jobId]['91'].value, 10);
          const subRoughHours = parseInt(res.data[jobId]['92'].value, 10);
          const subRoughAmount = parseInt(res.data[jobId]['95'].value, 10);
          const roughHours = parseInt(res.data[jobId]['96'].value, 10);
          const roughAmount = parseInt(res.data[jobId]['98'].value, 10);
          const finishHours = parseInt(res.data[jobId]['104'].value, 10);
          const finishAmount = parseInt(res.data[jobId]['107'].value, 10);
          const closeHours = parseInt(res.data[jobId]['477'].value, 10);
          const closeAmount = parseInt(res.data[jobId]['480'].value, 10);
          const actualHours = parseInt(res.data[jobId]['479'].value, 10);
          const actualAmount = parseInt(res.data[jobId]['224'].value, 10);

          setChartData({
            type: 'scatter',
              datasets: [
                {
                  label: 'TOTAL',
                  data: [
                    { x: designHours, y: designAmount },
                    { x: subRoughHours, y: subRoughAmount },
                    { x: roughHours, y: roughAmount },
                    { x: finishHours, y: finishAmount },
                    { x: closeHours, y: closeAmount }
                  ],
                  borderWidth: 2,
                  borderColor: '#4183c4',
                  backgroundColor: '#4183c4',
                  tension: 0.8,
                  spanGaps: true,
                  lineTension: 0.5,
                  showLine: true,
                  fill: false,
                  showTooltip: false,
                  pointBorderWidth: 1
                },
                {
                  label: 'ACTUALS',
                  data: [{ x: actualHours, y: actualAmount }],
                  fill: false,
                  borderColor: '#e34747',
                  backgroundColor: '#e34747',
                  borderWidth: 5,
                  showTooltip: false
                }
              ],
              options: {
                showAllTooltips: true,
                enabled: true,
                maintainAspectRatio: false,
                legend: {
                  display: true
                }
              }
          });
        })
      })
      .catch((err) => {
        console.log(err);
      });
    console.log(
      designHours,
      designAmount,
      subRoughHours,
      subRoughAmount,
      roughHours,
      roughAmount,
      finishHours,
      finishAmount,
      closeHours,
      closeAmount,
      actualHours,
      actualAmount
    );
  };

  useEffect(() => {
    chart();
  }, []);

  return (
    <div className="App">
      <div>
        <Scatter
          data={chartData}
          options={{
            responsive: true,
            title: { text: "Total Project", display: true },
            scales: {
              yAxes: [
                {
                  scaleLabel: {
                    display: true,
                    labelString: 'Dollar Amounts'
                  },
                  ticks: {
                    autoSkip: true,
                    maxTicksLimit: 10,
                    beginAtZero: true
                  },
                  gridLines: {
                    display: true
                  }
                }
              ],
              xAxes: [
                {
                  scaleLabel: {
                    display: true,
                    labelString: 'Hours'
                  },
                  gridLines: {
                    display: true
                  }
                }
              ],
            },
          }}
        />
      </div>
    </div>
  );
};

export default TotalLineChart;

请注意上面的评论,以显示我试图{jobId}从 TitleCycle.js放置的位置仅影响顶线图 *尝试将 API 正文更改为:where: "{3.EX. '{jobId}'}",

TitleCycle.js

import React from "react";

function TitleCycle({ data }) {
  const [index, setIndex] = React.useState(0);

  const jobId = data[index]['3'].value;

  // Set Timer and Interval based on Index
  React.useEffect(() => {
    const timerId = setInterval(
      () => setIndex((i) => (i + 1) % data.length),
      5000 // 5 seconds.
    );
    return () => clearInterval(timerId);
  }, [data]);
console.log(data[index]['3'].value)
  return data.length ? (
    <div className="TitleCycle">
      <h3>{data[index]["6"].value}</h3>
      {jobId}
    </div>
  ) : null;
}

export default TitleCycle;

标题.js

import { Component, React } from "react";
import TitleCycle from '../TitleCycle';
//import TotalLineChart from '../charts/TotalLineChart'

let headers = {
  "QB-Realm-Hostname": "XXXXXXXX.quickbase.com",
  "User-Agent": "FileService_Integration_V2.1",
  "Authorization": "QB-USER-TOKEN XXXXXXXXXXXXXX",
  "Content-Type": "application/json"
};

class Title extends Component {
  state = {
    data: null,
  };

  componentDidMount() {
    this.fetchData();
  }

  fetchData = () => {
    let body = {"from":"bpz99ram7","select":[3,6,40],"where": "{40.CT. 'In Progress'}","sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}};

    fetch("https://api.quickbase.com/v1/records/query", {
      method: "POST",
      headers: headers,
      body: JSON.stringify(body)
    })
      .then((response) => response.json())
      .then(({data}) => this.setState({ data }));
  };

  render() {
    const { data } = this.state;
    if (data === null) return "Loading...";

    return (
      <div className="Title">
        <TitleCycle data={data} />
      </div>
    );
  }
}

export default Title;

请注意,这只是将作业名称作为 {data} 发送到 TitleCycle.js 上面你可以看到一个作业名称,下面有记录 ID,它在设定的持续时间内与作业循环上面你可以看到它是如何更改和更新的记录 ID,所以现在我只想在我的 LineChart.js 文件中设置该字段,以便提取的数据是显示的任何数据在此处输入图像描述 在此处输入图像描述 {jobId}

现在只有顶线图正在生效,这是我在更改其他以匹配之前首先尝试的地方。希望这可以帮助!

应用程序.js

import './App.css'
// import Title from './components/header/Title'
import TotalLineChart from './components/charts/TotalLineChart'
import RadiantLineChart from './components/charts/RadiantLineChart'
import PlumbingLineChart from './components/charts/PlumbingLineChart'
import SnowmeltLineChart from './components/charts/SnowmeltLineChart'
import HVACLineChart from './components/charts/HVACLineChart'
import GasPipeLineChart from './components/charts/GasPipeLineChart'
import FixturesLineChart from './components/charts/FixturesLineChart'
// import JobsTableApi from './components/JobsTableApi'
import ClassBudgetsTableApi from './components/ClassBudgetsTableApi'
// import TitleCycle from './components/TitleCycle'
import Title from './components/header/Title'
// import Logo from './components/Logo';
// import CurrentTitleInfo from './components/CurrentTitleInfo'

function App() {

  return (
      <div>
        {/* <div className="flexbox-container">
          <div className="Logo">

         {/* </div> */}
                     {/* <Logo /> */}
          <div className="App">
            <Title />
          </div>
        {/* </div> */}
        <div className="TopChart">
          <TotalLineChart />
        </div>
        <div className="FirstRowContainer">
          <RadiantLineChart />
          <PlumbingLineChart />
          <FixturesLineChart />
        </div>
        <div className="SecondRowContainer">
          <SnowmeltLineChart />
          <HVACLineChart />
          <GasPipeLineChart />
        </div> 
        {/* <JobsTableApi /> */}
        <ClassBudgetsTableApi />
      </div>
  );
}

export default App;

更新 App.js

import React, { useEffect, useState } from "react";
import './App.css'
import Title from './components/header/Title'
import TotalLineChart from './components/charts/TotalLineChart'
import RadiantLineChart from './components/charts/RadiantLineChart'
import PlumbingLineChart from './components/charts/PlumbingLineChart'
import SnowmeltLineChart from './components/charts/SnowmeltLineChart'
import HVACLineChart from './components/charts/HVACLineChart'
import GasPipeLineChart from './components/charts/GasPipeLineChart'
import FixturesLineChart from './components/charts/FixturesLineChart'
// import TitleCycle from './components/TitleCycle'
// import Logo from './components/Logo';

let headers = {
  "QB-Realm-Hostname": "XXXXXXXX.quickbase.com",
  "User-Agent": "FileService_Integration_V2.1",
  "Authorization": "QB-USER-TOKEN XXXXXXXXX",
  "Content-Type": "application/json"
};

function App() {
  const [index, setIndex] = useState(0);
  const [allData, setAllData] = useState([]);

  // Fetch all data, all jobs
  useEffect(() => {
      function fetchData() {
          let body = {
              from: "bpz99ram7",
              select: [3, 6, 40],
              where: "{40.CT. 'In Progress'}",
              sortBy: [{ fieldId: 6, order: "ASC" }],
              groupBy: [{ fieldId: 40, grouping: "equal-values" }],
              options: { skip: 0, top: 0, compareWithAppLocalTime: false },
          };

          fetch("https://api.quickbase.com/v1/records/query", {
              method: "POST",
              headers: headers,
              body: JSON.stringify(body),
          })
          .then((response) => response.json())
          .then(({ data }) => setAllData(data));
  }
  fetchData();
}, []);

// Cycle through the jobIds and indexes
useEffect(() => {
  const timerId = setInterval(
      () => setIndex((i) => (i + 1) % allData.length),
      5000 // 5 seconds.
  );
  return () => clearInterval(timerId);
}, [allData]);

  // Calculate info based on index
  const jobId = allData[index]["3"].value;
  const title = allData[index]["6"].value;

  return (
      <div>
        {/* <div className="flexbox-container">
          <div className="Logo">
          {/* <Logo /> */}
         {/* </div> */}
        <div className="App">
          <Title title = {title}/>
        </div>
        <div className="TopChart">
          <TotalLineChart jobId = {jobId}/>
        </div>
        <div className="FirstRowContainer">
          <RadiantLineChart jobId = {jobId}/>
          <PlumbingLineChart jobId = {jobId}/>
          <FixturesLineChart jobId = {jobId}/>
        </div>
        <div className="SecondRowContainer">
          <SnowmeltLineChart jobId = {jobId}/>
          <HVACLineChart jobId = {jobId}/>
          <GasPipeLineChart jobId = {jobId}/>
        </div> 
      </div>
  );
}

export default App;

TypeError:无法读取未定义的属性“3”

{
  "data": [
    {
      "3": {
        "value": 316
      },
      "6": {
        "value": "1545 Indian Hills"
      },
      "40": {
        "value": "In Progress"
      }
    },
    {
      "3": {
        "value": 291
      },
      "6": {
        "value": "1547 Tomahawk"
      },
      "40": {
        "value": "In Progress"
      }
    },

标签: javascriptreactjschart.js

解决方案


似乎您需要jobId在 TitleCycle 组件和 LineChart 组件之间同步状态,以便两者都可以访问此变量,并且可以同步渲染内容(标题更改和 LineChart 也更改)。

在这种情况下,您需要jobId一个有点全局的变量。我想到了这个解决方案。

这似乎是当前的层次结构

App
|-- LineChart
|-- Title
    |-- TitleCycle

因此,将jobId共同祖先和道具放在一起向下钻取。在这种情况下,您不必从 TitleCycle 生成jobIdandindex并且必须将状态向上和环绕到 LineChart 中,而是应在 App.js 内循环索引。像这样的东西:

// App.js
function App() {
    const [allData, setAllData] = useState([]);
    const [index, setIndex] = useState(0);

    // Fetch all data, all jobs
    useEffect(() => {
        function fetchData() {
            let body = {
                from: "bpz99ram7",
                select: [3, 6, 40],
                where: "{40.CT. 'In Progress'}",
                sortBy: [{ fieldId: 6, order: "ASC" }],
                groupBy: [{ fieldId: 40, grouping: "equal-values" }],
                options: { skip: 0, top: 0, compareWithAppLocalTime: false },
            };

            fetch("https://api.quickbase.com/v1/records/query", {
                method: "POST",
                headers: headers,
                body: JSON.stringify(body),
            })
                .then((response) => response.json())
                .then(({ data }) => setAllData(data));
        }
        fetchData();
    }, []);

    // Cycle through the jobIds and indexes
    useEffect(() => {
        const timerId = setInterval(
            () => setIndex((i) => (i + 1) % allData.length),
            5000 // 5 seconds.
        );
        return () => clearInterval(timerId);
    }, [allData]);

    // Calculate info based on index
    const jobId = allData[index]?.["3"]?.value || "290"; // Default "290"
    const title = allData[index]?.["6"]?.value || "Default title"; 

    // Renders the components with the necessary data
    return (
        <div>
            <Title title={title} />
            <LineChart1 jobId={jobId} />
            <LineChart2 jobId={jobId} />
        </div>
    );
}

// LineChart.js
function TotalLineChart(props) {
    // Got the jobId from props
    const { jobId } = props;

    // Now make the request with this jobId
}

推荐阅读