我应该在哪里更新状态以从 API 获得正确的读数?

Where should I update state to get the correct readout from the API?

我有一个大项目要完成我作为开发人员的第一份工作的面试,所以我 a) 没有经验并且 b) 没有时间进行任何剧烈的重构(例如使用 Hooks)。

我的任务是从 API 中提取数据并生成一些图表。我(终于)弄清楚了如何使用 axios 获取我需要的数据,但是在尝试访问该对象时遇到了一个非常奇怪的问题。

父级 <App /> 将对象设置为如下状态:

  onDateChange = async date => {
    const response = await fastapi.get(`/${date}`)
    this.setState({apiData:response.data})
  }

当我记录状态时,我看到的正是我所期望的。所以我转到子组件 (<DataDisplay />),我需要将对象拆分成几个变量,这就是事情开始变得混乱的地方。我尝试console.log this.props.apiData.site_hours 处的数据,没有问题。我取回了一个包含两个键值对的小对象:{"inside": 3.19765, "outside": 4.64378}。但是当我尝试记录 this.props.apiData.site_hours.inside 时,它告诉我里面是未定义的!

我问了一个朋友,他是一个非常有经验的程序员,虽然不熟悉 React,他说很可能在我记录它的时候它只是未定义的。很奇怪,因为我可以在对象中获取上一层的信息,但是当我向下挖掘一层时,什么都没有!所以我试着移动我的 console.log,果然,当我把它放在 componentDidUpdate() 中时,site_hours.inside 存在!所以我尝试使用 componentDidUpdate() 来设置状态,但机器立即告诉我这是一个非常非常糟糕的主意。

很明显,这是生命周期和我尝试做事的顺序的问题,但 none 我的研究发现了任何有用的东西!我发现令人难以置信的是,我对该对象的调用将 return 仅提供部分信息,而不是全部信息。请帮忙!

https://github.com/erasebegin/react-projects/tree/master/tharsus-interface/src

已更新 => 试试这个,看看是否可以从 child 组件中记录道具:

import React from "react";
import MovementDataDisplay from "./MovementDataDisplay";
import OverviewDataDisplay from "./OverviewDataDisplay";
import DistributionDataDisplay from "./DistributionDataDisplay";

class DataDisplay extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { passAPIData } = this.props
    const {siteHours, movingHours} = passAPIData

    console.log(siteHours, movingHours)

    if (this.props.NavState === "movement") {
        return (
          <div className="chart-container">
            <MovementDataDisplay
              Data={passAPIData}
              Title="Site Hours"
              Labels={['inside','outside']}
            />
            <MovementDataDisplay
              Data={[1,3]}
              Title="Moving Hours"
            />
          </div>
        );
      } else if (this.props.NavState === "overview") {
        return (
          <div className="chart-container">
            <OverviewDataDisplay
              Data={[3.3176110809420893, 4.18238891905791]}
              Title="Site Hours"
            />
            <OverviewDataDisplay
              Data={[3.9176110809420893, 4.18238891905791]}
              Title="Moving Hours"
            />
          </div>
        );
      } else if (this.props.NavState === "distribution") {
        return <DistributionDataDisplay />;
      }
    }
}

export default DataDisplay;

没有拉回购并调试它,或者 运行 它(诚然)我的猜测是当你在 componentDidMount 中设置状态时你正在导致 re-render child 组件并丢失该值(只是一个猜测) - 在这里我已经删除了 state 一起(我看不出它是如何需要的)和 componentDidMount 并且正在记录你的值直接从道具中寻找。如果这些值设置为 parent 状态并作为道具传递,我没有任何理由(从你目前拥有的)看到你需要 re-set 他们在 child。到目前为止,除了为它设置道具外,你似乎甚至没有在 child 中使用状态,除非这些值将从这个 child 组件中本地更改......你可以(也应该)从 parent 的道具中读取它们 定义、更新和传递的。

再次更新(在摆弄实际代码之后;))

检查一下,看看它是否能解决您的问题。我为我所做的小改动添加了评论:

App.js:

import React from "react";
import Nav from "./components/Nav";
import DataDisplay from "./components/DataDisplay";
import "./styles/App.css";
import fastapi from './api/fastapi'

class App extends React.Component {
  constructor() {
    super();

    this.state = {
      userDate: "",
      apiData: {}, // this was set to a [] but it's data type is a actually an {}
      navState: "overview"
    };
    this.setNavState = this.setNavState.bind(this);
  }

  setNavState(nav) {
    // when triggering this from the UI it causes a state change, and a re-render
    const btnArr = ["overview", "movement", "distribution"];
    this.setState({ navState: btnArr[nav] });

    // this needs to be reset
    this.onDateChange(this.state.userDate)
  }

  onDateChange = async date => {
    const response = await fastapi.get(`/${date}`)
    console.log('onDateChange response.data', response.data)

    this.setState({ 
      apiData: response.data, 
      userDate: date ? date : this.state.userDate // you were not setting this but you will need it for when setNavState runs
    })
  }

  render() {
    console.log("new state is ",this.state.apiData) // you should see this
    return (
      <div className="App">
        <Nav getNav={this.setNavState} getDate={this.onDateChange} />
        <DataDisplay NavState={this.state.navState} apiData={this.state.apiData}/>
      </div>
    );
  }
}

export default App;

DateDisplay.js

import React from "react";
import MovementDataDisplay from "./MovementDataDisplay";
import OverviewDataDisplay from "./OverviewDataDisplay";
import DistributionDataDisplay from "./DistributionDataDisplay";

class DataDisplay extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    console.log("from DataDisplay: ", this.props.apiData)
    const { apiData : { site_hours = {} }} = this.props
    const {inside = 0, outside = 0} = site_hours // defaults to avoid breaking if there is no data here (prior to selecting date for ex)

    console.log(inside, outside) // i can see these logs in the child :) 

    if (this.props.NavState === "movement") {
        return (
          <div className="chart-container">
            <MovementDataDisplay
              Data={[inside, outside]} // you are putting the entire 'this.state' here (a big object), but it seems to want a simple array
              Title="Site Hours"
              Labels={['inside','outside']}
            />
            <MovementDataDisplay
              Data={[1,3]}
              Title="Moving Hours"
            />
          </div>
        );
      } else if (this.props.NavState === "overview") {
        return (
          <div className="chart-container">
            <OverviewDataDisplay
              Data={[3.3176110809420893, 4.18238891905791]}
              Title="Site Hours"
            />
            <OverviewDataDisplay
              Data={[3.9176110809420893, 4.18238891905791]}
              Title="Moving Hours"
            />
          </div>
        );
      } else if (this.props.NavState === "distribution") {
        return <DistributionDataDisplay />; // you need to pass this some data of course :) 
      }
    }
}

export default DataDisplay;

我可以在 child 中看到日志,不需要状态。 我看到 和 IDK 的问题,如果这就是您所说的,是在 Overview/Movement/Distribution 图表之间单击时分页。这是因为您的 api 响应存储到 parent 组件中的本地状态,当您通过在 Nav 中调用 setNavState 更新状态时,您会在其中触发 re-render parent 并且状态丢失,您需要重新获取数据 - 分页符是因为该数据不再可用 child 并且没有适当的保护措施来处理没有数据(这是您应该始终编码以备记录的内容)所以我

1) 保存状态中的日期 2) 和 re-fetch 它在同一个导航回调中(这可能有效,因为 setState 是异步的) 3) 向 child 添加了一些默认值以防止破坏

如果此应用程序具有全局状态,您可以将 apiResponse 存储在那里而不需要重新获取,或者您可以将其存储在本地存储或什至 cookie 中以避免重新获取。全局状态更可取。