Как использовать useEffect () для извлечения данных из API в настройках управления состоянием useReducer () и useContext ()? - PullRequest
1 голос
/ 27 мая 2020

reducer.js

export const reducer = (state, action) => {
  switch(action.type) {
    case SET_HEROES:
      return { ...state, heroes: action.heroes }
  }
}

AppContext.js

export const AppContext = React.createContext()

export const AppProvider = (props) => {
  const initialState = { 
    heroes: [] 
  }

  const [appState, dispatch] = useReducer(reducer, initialState)

  const setHeroes = async () => {
    const result = await getHeroes()
    dispatch({ type: SET_HEROES, heroes: result })
  }

  return <AppContext.Provider
    values={{ heroes: appState.heroes, setHeroes }}
  >
    {props.children}
  </AppContext.Provider>
}

HeroesScreen.js

const HeroesScreen = () => {
  const { heroes, setHeroes } = useContext(AppContext)

  useEffect(() => {
    setHeroes()
  }, [])

  return <>
  // iterate hero list
  </>
}

export default HeroesScreen

Выше простая настройка компонента с помощью reducer + контекст как управление государством. На экране отображаются герои, все работает нормально, но у меня появляется предупреждение Reach Hook useEffect has a missing dependency: 'setHeroes'. Но если я добавлю его как зависимость, оно будет sh мое приложение с Maximum depth update exceeded

Я искал, но все, что я вижу, это помещает вызов функции fetch внутри useEffect(). Я бы хотел извлечь функцию и поместить ее в отдельный файл в соответствии с принципом SRP

EDITED:

Как рекомендовано при использовании useCallback()

AppContext.js

const setHeroes = useCallback(() => {
    getHeroes().then(result => dispatch({ type: SET_HEROES, heroes: result }))
  }, [dispatch, getHeroes])

HeroesScreen.js

useEffect(() => {
  setHeroes()
}, [setHeroes])

Добавление getHeroes в качестве зависимости от useCallback, линтер показывает ненужную зависимость

Ответы [ 2 ]

3 голосов
/ 27 мая 2020

Если вы посмотрите на код в AppProvider, вы создаете новую функцию setHeroes каждый раз, когда она отрисовывается. Поэтому, если вы добавляете setHeroes в качестве зависимости к useEffect, код выполняет что-то вроде этого:

  1. AppProvider отображает, setHeroes создается, состояние - начальное состояние
  2. Где-то ниже по иерархии компонентов рендерится HeroesScreen. Вызывается useEffect, который, в свою очередь, вызывает setHeroes
  3. getHeroes и отправляет действие
  4. reducer изменяет состояние, которое приводит к повторному рендерингу AppProvider
  5. AppProvider рендерит, setHeroes создается с нуля
  6. useEffect выполняется снова, так как setHeroes изменено, и весь l oop повторяется вечно!

Чтобы решить эту проблему, вам действительно нужно добавить setHeroes как зависимость к useEffect, но затем обернуть его, используя useCallback:

const setHeroes = useCallback(async () => {
  const result = await getHeroes();
  dispatch({ type: SET_HEROES, heroes: result });
}, [getHeroes, dispatch]);
1 голос
/ 27 мая 2020

Хороший вопрос, у меня тоже была такая проблема, это моё решение. Я использую Typescript, но он также будет работать только с JS.

UserProvider.tsx

import * as React from 'react'
import { useReducer, useEffect } from 'react'
import UserContext from './UserContext'
import { getUser } from './actions/profile'
import userReducer, { SET_USER } from './UserReducer'

export default ({ children }: { children?: React.ReactNode }) => {
  const [state, dispatch] = useReducer(userReducer, {})

  const getUserData = async () => {
    const { user } = await getUser()
    dispatch({ type: SET_USER, payload: { ...user } })
  }

  useEffect(() => {
    getUserData()
  }, [])

  return (
    <UserContext.Provider value={{ user: state }}>
      {children}
    </UserContext.Provider>
  )
}

Затем оберните ваше приложение поставщиком

index.tsx

<UserProvider>
  <App />
</UserProvider>

Затем, чтобы использовать потребителя контекста, я делаю это

AnyComponent.tsx

<UserConsumer>
   {({ user }) => {
     ...
   }} 
</UserConsumer

или вы также можете использовать его так

const { user } = useContext(UserContext)

Сообщите мне, работает ли

...