useEffect 与 useContext 陷入无限循环

Posted

技术标签:

【中文标题】useEffect 与 useContext 陷入无限循环【英文标题】:useEffect stuck in infinite loop with useContext 【发布时间】:2021-12-13 05:13:19 【问题描述】:

我有一个功能组件Pets,它显示了当前登录用户的所有宠物。在我的Pets 组件内部,我使用useEffectuseState 来实现这一点。

const [pets, setPets] = useState([]);

useEffect(() => 
    const fetchPets = async () => 
      try 
        const  data  = await axios.get('/pet/mypets');
        setPets(data.pets);
       catch (error) 
        console.log(error);
      
    ;

    fetchPets();
  , []);

然后我逐行渲染表格中的所有宠物。问题是当我尝试使用useContext 执行此操作时,它最终陷入了无限循环。例如,我有一个名为petsContext 的文件,其代码如下...

import React from 'react';

const PetsContext = React.createContext(
  pets: [],
  onRegister: (first_name, last_name, breed, age, weight) => ,
  onUpdate: (first_name, last_name, breed, age, weight) => ,
  onDelete: () => ,
  onFetch: () => ,
);

export default PetsContext;

我有一个petsProvider 文件,其中包含以下内容...

import React,  useReducer  from 'react';
import PetContext from './pets-context';
import axios from 'axios';

const initialState = 
  pets: [],
  message: '',
  messageType: '',
;

const petReducer = (state = initialState, action) => 
  switch (action.type) 
    case 'FETCH_PETS_SUCCESS':
      return 
        ...state,
        pets: action.payload,
      ;
    case 'REGISTER_PET_SUCCESS':
      if (state.pets === undefined) 
        state.pets = [];
      

      return 
        ...state,
        pets: [action.payload.pet, ...state.pets],
        message: action.payload.message,
      ;
    case 'REGISTER_PET_FAIL':
      return 
        ...state,
        message: 'Unable to register pet!',
      ;
    default:
      return initialState;
  
;

const PetProvider = (props) => 
  const [petState, dispatchPetAction] = useReducer(petReducer, 
    petReducer,
    initialState,
  );

  const registerHandler = async (first_name, last_name, breed, age, weight) => 
    const config = 
      headers: 
        'Content-Type': 'application/json',
      ,
    ;

    age = parseInt(age);
    weight = parseFloat(weight);

    const body = JSON.stringify( first_name, last_name, breed, age, weight );

    try 
      const  data  = await axios.post('/pet/register', body, config);

      dispatchPetAction( type: 'REGISTER_PET_SUCCESS', payload: data );
     catch (error) 
      console.log(error.response);
      // dispatchPetAction(
      //   type: 'REGISTER_PET_FAIL',
      //   payload: 
      //     message: error.response.data.message,
      //     messageType: 'danger',
      //   ,
      // );
    
  ;

  const fetchHandler = async () => 
    try 
      const  data  = await axios.get('/pet/mypets');
      dispatchPetAction( type: 'FETCH_PETS_SUCCESS', payload: data.pets );
     catch (err) 
      console.log(err);
    
  ;

  return (
    <PetContext.Provider
      value=
        pets: petState.pets,
        onRegister: registerHandler,
        onFetch: fetchHandler,
      
    >
      props.children
    </PetContext.Provider>
  );
;

export default PetProvider;

在我的Pets 组件中,而不是在我拥有以下内容之前显示的内容...

const petsContext = useContext(PetsContext);

useEffect(() => 
  petsContext.onFetch();
, [petsContext]);

// now I want to just access my pets by `petsContext.pets`
// which works but ends up in an infinite loop and I am not sure why.

我该如何解决这个无限循环,为什么会这样?

【问题讨论】:

【参考方案1】:

无限循环从您的上下文开始。

    您的上下文值会为每次渲染创建。 由于您的上下文值已更改,因此效果调用 fetch 获取更新状态,继续触发渲染。因为触发了渲染,所以 First 将被触发。

修复它:

  // wrap fetchHandler in useCallback
  const fetchHandler = useCallback(async () => 
    try 
      const  data  = await axios.get('/pet/mypets');
      dispatchPetAction( type: 'FETCH_PETS_SUCCESS', payload: data.pets );
     catch (err) 
      console.log(err);
    
  , [dispatchPetAction]);

  const  onFetch  = useContext(PetsContext);

  // this effect should depend on onFetch, not petsContext
  // now, it will only being call if dispatchPetAction is changed
  // dispatchPetAction -> fetchHandler -> useEffect
  useEffect(() => 
    onFetch();
  , [onFetch]);

https://reactjs.org/docs/hooks-reference.html#usecallback

【讨论】:

非常感谢。有效。我将阅读更多关于 useCallback 实际工作原理的信息,以便我可以理解这一点。 很高兴为您提供帮助。 ?【参考方案2】:

useEffect 无限渲染的问题是由于依赖数组。如果您正在更新效果内的状态,但效果取决于您正在更新的状态,那么只要您的 petsContext 中的任何内容发生更改,就会调用该效果。

这意味着当上下文改变时调用效果,然后它改变上下文,它再次调用自身 ad infinitum 直到你得到一个 Stack Overflow (rimshot)。

要解决此问题,请确定您希望此效果依赖于什么,并将其最小化到该对象。 onFetch 在您的示例中被声明为一个空函数,所以我不能 100% 确定它在做什么,或者我会进一步建议您。

【讨论】:

好的。现在不在我的电脑上,但会试一试。我猜我可以使用useState 并且我的状态没有改变,那么我不应该处于这个无限循环中吗? onFetch 只是抓取一个宠物列表... [ fn: 'pets first name', ln: 'pets last name' ] 等等。 onFetch 在我的PetProvider 中指向fetchHandler。本质上,它只是向我的 Flask 后端发出请求以从我的数据库中获取数据。

以上是关于useEffect 与 useContext 陷入无限循环的主要内容,如果未能解决你的问题,请参考以下文章

如何将 React 钩子(useContext、useEffect)与 Apollo 反应钩子(useQuery)结合起来

了解函数化组件 HooksuseState,useEffect,useContext,useReducer

了解函数化组件 HooksuseState,useEffect,useContext,useReducer

从 REST API 填充上下文并在带有 useEffect 和 useContext 的 React 组件中使用它

useContext 未在子组件中显示更新的状态(当从全局文件中的 useEffect 挂钩中的 firebase 检索数据时)

React useEffect useContext - 上下文适用于某些组件,但不适用于动态路由