ReactJS: ошибка максимальной глубины обновления, превышенная при загрузке в useEffect - PullRequest
0 голосов
/ 11 июля 2019

В React 16.8 я реализовал свой проект с помощью useReducer, перехватил useContext и создал систему глобального управления состоянием, аналогичную Redux.

В представлении, когда я пытался извлечь данные в useEffect,это вызывает ошибку максимальной глубины обновления.

Я уже попробовал все примеры в Facebook React - Hooks-FAQ , но не могу решить проблему.

Мой пакет.json такой:

    "prop-types": "^15.7.2",
    "react": "^16.8.6",
    "react-app-polyfill": "^1.0.1",
    "react-chartjs-2": "^2.7.6",
    "react-dom": "^16.8.6",
    "react-router-config": "^5.0.0",
    "react-router-dom": "^5.0.0",
    "react-test-renderer": "^16.8.6",
    "react-uuid": "^1.0.2",
    "reactstrap": "^7.1.0",
    "simple-line-icons": "^2.4.1",
    "styled-components": "^4.2.0"

Вот мой пример кода:

Вот View.js

import React, { useEffect, useRef } from 'react'
import useView from '/store/hooks/useView'
import isEqual from '/services/isEqual'
import loading from '/service/loading'

const View = () => {
    const viewContext = useView()
    let viewContextRef = useRef(viewContext)

    // Keep latest viewContext in a ref
    useEffect(() => {
        viewContextRef.current = viewContext
    })

    useEffect(() => {

        // Fetch Data
        async function fetchData() {
            // This causes the loop
            viewContextRef.current.startFetchProcess()

            const url = 'http://example.com/fetch/data/'

            try {
                const config = {
                    method: 'POST',
                    headers: {
                        Accept: 'application/json',
                        'Content-Type': 'application/json',
                    }
                }

                const response = await fetch(url, config)

                if (response.ok) {
                    const res = await response.json()
                    finalizeGetViewList(res)

                    // This causes the loop
                    viewContextRef.current.stopFetchProcess()

                    return res
                } 
            } catch (error) {
                console.log(error)
                return error
            }
        }

        // Prepare data for rows and update state
        const finalizeGetViewList = (data) => {

            const { Result } = data

            if (Result !== null) {

                let Arr = []

                for (let i = 0; i < Result.length; i++) {
                    let Obj = {}
                    //...
                    //...
                    Arr.push(Obj)
                }

                // I compare the prevState with the fetch data to reduce 
                // the number of update state and re-render, 
                // so this section do not cause the problem

                if (!isEqual(roleContextRef.current.state.rows, Arr)) {
                    viewContextRef.current.storeViewList(Arr)
                }

            } else {
                console.log(errorMessage)
            }
        }

        function doStartFetch () {
                fetchData()
        }

        const startingFetch = setInterval(doStartFetch, 500)
        // aborting request when cleaning
        return () => {
            clearInterval(startingFetch)
        }
    }, [])

    const {
      rows,
      isLoading
    } = viewContext.state

    if (isLoading) {
        return (loading())
    } else {
        return (
          <div>
            {rows.map(el => (
            <tr key={el.id}>
              <td>el.name</td>
              <td>el.price</td>
              <td>el.discount</td>
            </tr>
            ))}
          </div>  
        )
    }
}

export default View

Если вы действительно хотите решить эту проблему, пожалуйста,взгляните на другие файлы цикла хранения.

Вот хук useView.js:

import { useContext } from 'react'
import { StoreContext } from "../providers/Store"

export default function useUsers() {
  const { state, actions, dispatch } = useContext(StoreContext)

  const startFetchProcess = () => {
    dispatch(actions.viewSystem.startFetchProcess({
      isLoading: true
    }))
  }

  const storeViewList = (arr) => {
    dispatch(actions.viewSystem.storeViewList({
      rows: arr
    }))
  }

  const stopFetchProcess = () => {
    dispatch(actions.viewSystem.stopFetchProcess({
      isLoading: false
    }))
  }

  return {
    state: state.viewSystem,
    startFetchProcess,
    storeViewList,
    stopFetchProcess,
  }
}

Вот viewReducer.js для отправки:

const types = {
    START_LOADING: 'START_LOADING',
    STORE_VIEW_LIST: 'STORE_VIEW_LIST',
    STOP_LOADING: 'STOP_LOADING',
}

export const initialState = {
    isLoading: false,
    rows: [
      {
        ProfilePicture: 'Avatar',
        id: 'id', Name: 'Name', Price: 'Price', Discount: 'Discount'
      }
    ],
  }

  export const actions = {
    storeViewList: (data) => ({ type: types.STORE_VIEW_LIST, value: data }),
    startFetchProcess: (loading) => ({ type: types.START_LOADING, value: loading }),
    stopFetchProcess: (stopLoading) => ({ type: types.STOP_LOADING, value: stopLoading })
  }

  export const reducer = (state, action) => {
    switch (action.type) {

        case types.START_LOADING:
          const Loading = { ...state, ...action.value }
          return Loading

        case types.STORE_VIEW_LIST:
            const List = { ...state, ...action.value }
            return List

        case types.STOP_LOADING:
          const stopLoading = { ...state, ...action.value }
          return stopLoading

        default:
          return state;
      }
  }

  export const register = (globalState, globalActions) => {
    globalState.viewSystem = initialState;
    globalActions.viewSystem = actions;
  }

Это StoreProvider для предоставления каждого компонента в приложении и передачи состояния:

import React, { useReducer } from "react"
import { reducer, initialState, actions } from '../reducers'

export const StoreContext = React.createContext()

export const StoreProvider = props => {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <StoreContext.Provider value={{ state, actions, dispatch }}>
      {props.children}
    </StoreContext.Provider>
  )
}

Это index.js редукторов для клонирования многих редукторов для разных представлений:

import { user as userData, reducer as loginReducer } from './loginReducer'
import { register as viewRegister, reducer as viewReducer } from './viewReducer'
import { register as groupRegister, reducer as groupsReducer } from './groupsReducer'


export const initialState = {};
export const actions = {};

userData(initialState, actions)
viewRegister(initialState, actions)
groupRegister(initialState, actions)

export const reducer = (state, action) => {
  return {
    credentials: loginReducer(state.credentials, action),
    roleSystem: viewReducer(state.viewSystem, action),
    groups: groupsReducer(state.groups, action)
  }
}

Извините за множество файлов, но другого способа объяснить ситуацию нет.Люди, которые раньше работали с Redux, могут понять этот подход.Нет проблем с системой отправки state => action =>, пока я не попытаюсь извлечь данные с начальным рендерингом страницы (в этом примере я назвал это View).

Классический подход let didCancel = false не сделалРабота.Проблема была решена, если я сравниваю состояние с новыми извлеченными данными.Но когда я добавил загрузку, он запускает useReducer и повторно отображает страницу, что вызывает бесконечный цикл.

UseRef и clearInterval не предотвращают это, и возникает эта ошибка:

Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

1 Ответ

0 голосов
/ 11 июля 2019

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

useEffect(() => {
  viewContextRef.current.startFetchProcess()
}, [])



 useEffect(() => {
 // Fetch Data
  async function fetchData () {
    // This causes the loop
    // moved to the dependency array
    const url = 'http://example.com/fetch/data/'

  // ..... //

  function doStartFetch () {
    roleContext.state.isLoading && fetchData()
  }

  const startingFetch = setInterval(doStartFetch, 500)
  // aborting request when cleaning
  return () => {
    clearInterval(startingFetch)
  }
}, [roleContext.state.isLoading])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...