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

时间:2018-11-13 05:26:31

标签: javascript reactjs recursion fetch-api

首先,我想让您知道我只是从React和Context API开始。

这是我的情况:我想在应用启动时从PunkAPI提取所有啤酒(在提取时显示加载程序),并将所有这些啤酒放在上下文中以将其访问到子目录中组件。

我的问题是API并未提供所有啤酒页面的总数,因此我必须检查结果的长度并在长度等于0时停止提取,然后将数据置于状态。

然后,一旦所有内容加载完毕,我想向所有啤酒添加属性(收藏夹)并将其设置为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')

2 个答案:

答案 0 :(得分:1)

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

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

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

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

因此在下一行

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

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

如果您使用诺言,那么您要等到获取新的啤酒数据后再运行forEach,就像这样:

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

您要执行此操作是因为您的以下几行取决于获取啤酒数据的完成情况。

答案 1 :(得分:0)

感谢@Shawn Andrews的帮助,我现在更加了解Promises背后的流程。最后,我决定将自己的收藏夹和啤酒合并到一个Children Container Component中,让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