【问题标题】:Where should I update state to get the correct readout from the API?我应该在哪里更新状态才能从 API 获得正确的读数?
【发布时间】:2020-03-20 01:00:41
【问题描述】:

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

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

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

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

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

我问了一个朋友,他是一位经验丰富的程序员,虽然不熟悉 React,他说在我记录它时它可能只是未定义。很奇怪,因为我可以在对象中获得更高一级的信息,但是当我深入一级时,什么也没有!所以我试着移动我的console.log,果然,当我把它放在componentDidUpdate() 中时,site_hours.inside 存在!所以我尝试使用componentDidUpdate() 来设置状态,但很快就被机器告知这是一个非常非常糟糕的主意。

很明显,这是生命周期和我尝试做事的顺序的问题,但我的研究都没有发现任何有用的东西!令人难以置信的是,我对这个对象的调用只会返回一些信息,而不是全部信息。请帮忙!

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

【问题讨论】:

  • 可能是您以某种方式触发了无意的重新渲染,并且在您看到它记录时数据未定义,我看到您正在设置状态很多,请记住状态更改会触发重新渲染
  • 在您的DataDisplay 中,您是否有理由将作为道具传递的数据设置为 componentDidMount 中的状态(您在此处触发重新渲染)或将其设置为状态?你不能直接显示来自props 的数据吗?我注意到您实际上并没有从该组件中 更新 (或以其他方式使用状态)...所以您真的需要那里的状态吗?

标签: json reactjs rest api lifecycle


【解决方案1】:

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

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;

没有拉出 repo 并调试它,或者运行它(诚然)我的猜测是,当您在子组件的 componentDidMount 中设置状态并丢失该值时,您会导致重新渲染(只是猜测) - 在这里我已经一起删除了state(我看不出它是如何需要的)和componentDidMount,并直接从道具中记录你正在寻找的值。如果这些值是在父状态中设置并作为道具传递的,那么我没有任何理由可以看到(从您目前拥有的)您需要重新设置它们以再次在子状态中声明。到目前为止,除了为它设置道具之外,您甚至似乎没有在子组件中使用任何状态,除非这些值将在此子组件中本地更改……您可以(并且应该)只阅读它们来自定义、更新和传递它们 的父级的 props。

更新再次(在修改了实际代码之后;))

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

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;

日期显示.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;

我可以看到孩子的日志,不需要状态。 我看到和 IDK 的问题,如果这就是您所说的,是在概览/运动/分布图表之间单击时分页。这是因为您的 api 响应存储到父组件中的本地状态,当您通过在导航中调用 setNavState 更新状态时,您会在该父组件中触发重新渲染并且状态丢失,您需要重新获取数据 - 分页是因为数据不再可供该孩子使用,并且没有适当的保护措施来处理没有数据(这是您应该始终为记录编码的内容)所以我

1) 保存状态中的日期 2)并在同一个导航回调中重新获取它(这可能有效,因为 setState 是异步的) 3) 为孩子添加了一些默认值以防止破坏

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

【讨论】:

  • 你说得对,我在这里不需要使用状态,我只是不知道有任何其他方法可以访问render() 中的变量,除非它来自状态或道具。由于我必须创建一大堆变量,为了整洁,我不想在 render() 方法中正确执行它。无论如何,我尝试了您的解决方案(谢谢!),但可惜它返回 undefined 对于 siteHours 和 MovingHours。
  • 我忘了提一些非常重要的事情:我在&lt;App /&gt; 组件中得到了相同的行为!当我尝试记录this.state.apiData 时,我得到了整个对象。然后this.state.apiData.site_hours 让我下一个对象,然后site_hours.inside 未定义!我想也许即使在 ` 中使用状态也不是一个好主意,但这是专业人士在参考项目中这样做的方式......
  • 如果我在&lt;App /&gt; 组件中的onDateChange() 方法中记录this.state.apiData.site_hours.inside,我似乎也得到了预期的结果。请帮忙,这让我很生气!
  • 如果我提取 repo 并运行它,或者在 env 文件中的请求中使用了一些 api 密钥,这会起作用吗?
  • 使用状态可以在 any 组件中使用,我只是向您展示它不是必需的,实际上可能会导致重新渲染,有时会导致问题,它是只是了解状态如何工作的东西......所以我试图通过在不需要它时删除它来消除它是罪魁祸首......我在代码中看不到任何导致但我可以尝试运行它并调试它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-22
  • 2011-12-04
  • 1970-01-01
  • 2021-10-17
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多