React получить все данные из 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.info(allBeers.length)
    console.info(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

PS: Я использую 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')
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
2 636
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В сообщении об ошибке говорится, что вы пытаетесь пройти через пустой массив beers = null в вашем defaultBeersContext.

Я думаю, вы намеревались загрузить данные пива из API, чтобы заполнить состояние пива затем forEach через этот заполненный массив пива.

Вы получаете сообщение об ошибке, потому что следующая строка является асинхронной и не завершена до того, как вы попытаетесь получить доступ к возвращенным данным о пиве:

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

Поэтому на следующей строке

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

Состояние по-прежнему содержит старые данные по пиву по умолчанию (beers: null), потому что ваша предыдущая строка не завершила выборку данных о пиве.

Если вы используете обещания, вам нужно подождать, пока вы получите новые данные о пиве, прежде чем запускать forEach, например:

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

Вы хотите сделать это, потому что следующие строки зависят от завершения получения данных о пиве.

спасибо @Shawn Andrews за вашу помощь, теперь я больше понимаю процесс, который стоит за Promises. В конце концов я решил переместить свое слияние моих избранных и моих сортов пива в компонент-контейнер для детей, позволив 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

Другие вопросы по теме