React 在启动时从 api 获取所有数据

React fetch all data from api at startup

首先,我想让你知道我刚刚开始使用 React 和 Context API。

这是我的情况:我想在应用程序启动时从 PunkAPI 获取所有啤酒(在获取时显示加载器)并将所有这些啤酒放在上下文中以访问它sub-components.

我的问题是 API 没有给出所有啤酒页面的总数,所以我必须检查结果的长度并在长度等于 0 时停止获取,然后将数据放入国家。

然后,一旦所有内容都加载完毕,我想为所有啤酒添加一个 属性(favorite)并先将其设置为 false,然后,如果用户已登录,则检索他的收藏夹并在收藏夹列表所涉及的上下文中为每种啤酒将其设置为 true。

这是我现在带来的:

import React from 'react'
import {jsonFetch} from 'easyfetch'
import _ from 'lodash'
import {UserAuthContext} from './userContext'
import {db} from '../firebase'

const defaultBeersContext = {
  beers: null,
  isFetchingBeers: true,
  errorFetchingBeers: null,
}

export const BeersContext = React.createContext(defaultBeersContext)

export default class BeersProvider extends React.Component {
  constructor(props) {
    super(props)
    this.state = defaultBeersContext
  }

  async componentDidMount() {
    let allBeers = await this.fetchAllBeers(1, [])
    allBeers.forEach(beer => (beer.favorite = false))
    console.log(allBeers.length)
    console.log(allBeers)
    const {isUserSignedIn, user} = this.context
    if (isUserSignedIn && user) {
      db.collection('users')
        .doc(user.uid)
        .get()
        .then(doc => {
          const favoriteBeers = doc.data().favorites
          favoriteBeers.forEach(elem => {
            const index = allBeers.findIndex(beer => beer.id === elem)
            allBeers[index].favorite = true
          })
        })
    }
    this.setState({beers: allBeers, isFetchingBeers: false})
  }

  fetchAllBeers(page, beers) {
    jsonFetch(`https://api.punkapi.com/v2/beers?page=${page}&per_page=80`)
      .get()
      .then(data => {
        if (data && data.length > 0) {
          this.fetchAllBeers(page + 1, _.concat(beers, data))
        } else {
          return beers
        }
      })
      .catch(error => this.setState({errorFetchingBeers: error, isFetchingBeers: false}))
  }

  render() {
    const {children} = this.props
    const {beers, isFetchingBeers, errorFetchingBeers} = this.state
    return (
      <BeersContext.Provider value={{beers, isFetchingBeers, errorFetchingBeers}}>{children}</BeersContext.Provider>
    )
  }
}

BeersProvider.contextType = UserAuthContext

P.S:我使用的 jsonFetch 来自这里:easyfetch

但那是行不通的,说:

 Unhandled Rejection (TypeError): Cannot read property 'forEach' of null

  21 | async componentDidMount() {
  22 |   this.fetchAllBeers(1, []).then(result => this.setState({beers: result}))
> 23 |   this.state.beers.forEach(beer => (beer.favorite = false))
     | ^  24 |   const {isUserSignedIn, user} = this.context
  25 |   if (isUserSignedIn && user) {
  26 |     db.collection('users')

在错误中,它指出您正在尝试遍历 defaultBeersContext 中的空 beers = null 数组。

我认为您的意图是从 API 加载啤酒数据以填充啤酒的状态,然后通过填充的啤酒数组加载啤酒数据。

您收到错误是因为以下行是异步的,并且在您尝试访问其返回的啤酒数据之前未完成:

this.fetchAllBeers(1, []).then(result => this.setState({beers: result}))

因此在下一行

this.state.beers.forEach(...)

状态仍然包含旧的默认啤酒默认数据(啤酒:空),因为您的上一行尚未完成获取啤酒数据。

如果您正在使用 promises,那么您需要等到在 运行 您的 forEach 之前获取了新的啤酒数据,就像这样:

this.fetchAllBeers(1, [])
    .then(result => {
        this.setState({beers: result});
        allBeers.forEach(beer => (beer.favorite = false));
        ...
        })

您想这样做是因为您的后续行取决于获取啤酒数据的完成。

感谢@Shawn Andrews 的帮助,我现在对 Promises 背后的过程有了更多的了解。我最终决定将我的收藏夹和啤酒合并到 Children 容器组件中,让 beersContext 获取所有内容,而 userContext 获取收藏夹。然后,容器组件将两者合并,然后将修改后的值传递给渲染组件!

这是我拥有的最终 BeersContext:

import React from 'react'
import {jsonFetch} from 'easyfetch'
import _ from 'lodash'
import {UserAuthContext} from './UserContext'

const defaultBeersContext = {
  beers: null,
  isFetchingBeers: true,
  errorFetchingBeers: null,
}

export const BeersContext = React.createContext(defaultBeersContext)

export default class BeersProvider extends React.Component {
  constructor(props) {
    super(props)
    this.state = defaultBeersContext
  }

  componentDidMount() {
    this.fetchAllBeers(1, [])
  }

  async fetchAllBeers(page, beers) {
    await jsonFetch(`https://api.punkapi.com/v2/beers?page=${page}&per_page=80`)
      .get()
      .then(data => {
        if (data && data.length > 0) {
          data.forEach(item => (item.favorite = false))
          this.fetchAllBeers(page + 1, _.concat(beers, data))
        } else {
          this.setState({beers, isFetchingBeers: false})
        }
      })
      .catch(error => this.setState({errorFetchingBeers: error, isFetchingBeers: false}))
  }

  render() {
    const {children} = this.props
    const {beers, isFetchingBeers, errorFetchingBeers} = this.state
    return (
      <BeersContext.Provider value={{beers, isFetchingBeers, errorFetchingBeers, fetchAllBeers: this.fetchAllBeers}}>
        {children}
      </BeersContext.Provider>
    )
  }
}

BeersProvider.contextType = UserAuthContext