Прежде всего, я хотел бы сообщить вам, что я только начинаю работать с 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')



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


В сообщении об ошибке говорится, что вы пытаетесь пройти через пустой массив 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