【问题标题】:infinite loop when querying api in redux action在 redux 操作中查询 api 时的无限循环
【发布时间】:2020-06-30 03:47:57
【问题描述】:

我正在尝试通过 redux-thunk 操作查询我的 Firebase 后端,但是,当我在使用 useEffect() 的初始渲染中这样做时,我最终会遇到以下错误:

Error: 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.

我的操作只是返回一个 Firebase 查询快照,然后我在 reducer 中收到了该快照。我使用钩子来调度我的操作:

export const useAnswersState = () => {
    return {
        answers: useSelector(state => selectAnswers(state)),
        isAnswersLoading: useSelector(state => selectAnswersLoading(state))
    }
}

export const useAnswersDispatch = () => {
    const dispatch = useDispatch()
    return {
        // getAnswersData is a redux-thunk action that returns a firebase snapshot
        setAnswers: questionID => dispatch(getAnswersData(questionID))
    }
}

以及以下选择器从我的快照和 redux 状态中获取我需要的数据:

export const selectAnswers = state => {
    const { snapshot } = state.root.answers
    if (snapshot === null) return []
    let answers = []
    snapshot.docs.map(doc => {
        answers.push(doc.data())
    })
    return answers
}

export const selectAnswersLoading = state => {
    return state.root.answers.queryLoading || state.root.answers.snapshot === null
}

在我的实际组件中,我首先尝试通过调度我的操作来查询我的后端,然后在加载数据后尝试读取结果数据,如下所示:

const params = useParams() // params.id is just an ID string

const { setAnswers, isAnswersLoading } = useAnswersDispatch()
const { answers } = useAnswersState()

useEffect(() => {
    setAnswers(params.id)
}, [])

if (!isAnswersLoading)) console.log(answers)

为了澄清,我正在使用我的useAnswersDispatch 来调度一个返回 Firebase 数据快照的 redux-thunk 操作。然后我使用我的useAnswersState 钩子在数据加载后访问数据。我正在尝试在我的实际视图组件的useEffect 中分派我的查询,然后使用我的状态挂钩显示数据。

但是,当我尝试打印 answers 的值时,我从上面收到错误消息。我将非常感谢任何帮助,如果有帮助,我很乐意提供更多信息,但是,我已经测试了我的减速器和动作本身,它们都按预期工作,所以我相信问题出在文件中如上所述。

【问题讨论】:

  • 嘿,除非您依赖setAnswers, isAnswersLoading, answers 触发更改其中之一的操作,否则我看不出您是如何进入无限循环的。你能创建一个小代码框或其他东西来展示你的问题吗
  • 但是,您可以尝试在 return export const useAnswersState = () => { const answers = useSelector(state => selectAnswers(state)); const isAnswersLoading = useSelector(state => selectAnswersLoading(state)); return { answers, isAnswersLoading } } 之外执行挂钩
  • 应该有更多代码导致无限循环,如果您正在寻找答案,您可以制作一个最小的代码框。

标签: javascript reactjs firebase redux react-redux


【解决方案1】:

尝试重构您的动作创建器,以便在效果内调用dispatch。您需要根据效果触发进行调度。

See related

const setAnswers = (params.id) => {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(useAnswersDispatch(params.id));
  }, [])
}

假设getAnswersData 是一个选择器,效果将触发调度到您的应用程序状态,当您收到响应时,您的选择器getAnswersData 会选择您想要的字段。

我不确定params.id 来自哪里,但您的组件依赖它来确定来自应用程序状态的答案。

触发调度后,只有应用程序状态会更新,组件状态不会更新。使用useDispatch 设置一个变量,您可以在组件的生命周期中对您的redux store 的调度函数进行变量引用。

要回答您的问题,如果您希望它处理多个分派,请将 params.iddispatch 添加到效果中的依赖项数组中。

// Handle null or undefined param.id
const answers = (param.id) => getAnswersData(param.id);
const dispatch = useDispatch();
useEffect(() => {
     if(params.id) 
        dispatch(useAnswersDispatch(params.id));
  }, [params.id, dispatch]);

console.log(answers);

【讨论】:

  • hmm 好的,所以这不是我真正要问的,我正在尝试创建一个挂钩来调度我的 getAnswersData 操作,它返回一个数据快照,它不是一个选择器,而是一个api 调用。然后我使用useAnswersState 挂钩来实际访问数据。如果不清楚,我深表歉意
【解决方案2】:

如评论;我认为您无限循环的实际代码依赖于setAnswers。在您的问题中,您忘记添加此依赖项,但下面的代码显示了如何防止 setAnswers 更改并导致无限循环:

const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
  const { type, payload } = action;
  console.log('in reducer', type, payload);
  if (type === GOT_DATA) {
    return { ...state, data: payload };
  }
  return state;
};

//I guess you imported this and this won't change so
//   useCallback doesn't see it as a dependency
const getAnswersData = id => ({
  type: GOT_DATA,
  payload: id,
});

const useAnswersDispatch = dispatch => {
  // const dispatch = useDispatch(); //react-redux useDispatch will never change
  //never re create setAnswers because it causes the
  //  effect to run again since it is a dependency of your effect
  const setAnswers = React.useCallback(
    questionID => dispatch(getAnswersData(questionID)),
    //your linter may complain because it doesn't know
    //  useDispatch always returns the same dispatch function
    [dispatch]
  );
  return {
    setAnswers,
  };
};

const Data = ({ id }) => {
  //fake redux
  const [state, dispatch] = React.useReducer(reducer, {
    data: [],
  });

  const { setAnswers } = useAnswersDispatch(dispatch);
  React.useEffect(() => {
    setAnswers(id);
  }, [id, setAnswers]);
  return <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
  const [id, setId] = React.useState(88);
  return (
    <div>
      <button onClick={() => setId(id => id + 1)}>
        increase id
      </button>
      <Data id={id} />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

这是导致无限循环的原始代码,因为 setAnswers 不断变化。

const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
  const { type, payload } = action;
  console.log('in reducer', type, payload);
  if (type === GOT_DATA) {
    return { ...state, data: payload };
  }
  return state;
};

//I guess you imported this and this won't change so
//   useCallback doesn't see it as a dependency
const getAnswersData = id => ({
  type: GOT_DATA,
  payload: id,
});

const useAnswersDispatch = dispatch => {
  return {
    //re creating setAnswers, calling this will cause
    //  state.data to be set causing Data to re render
    //  and because setAnser has changed it'll cause the
    //  effect to re run and setAnswers to be called ...
    setAnswers: questionID =>
      dispatch(getAnswersData(questionID)),
  };
};
let timesRedered = 0;
const Data = ({ id }) => {
  //fake redux
  const [state, dispatch] = React.useReducer(reducer, {
    data: [],
  });
  //securit to prevent infinite loop
  timesRedered++;
  if (timesRedered > 20) {
    throw new Error('infinite loop');
  }
  const { setAnswers } = useAnswersDispatch(dispatch);
  React.useEffect(() => {
    setAnswers(id);
  }, [id, setAnswers]);
  return <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
  const [id, setId] = React.useState(88);
  return (
    <div>
      <button onClick={() => setId(id => id + 1)}>
        increase id
      </button>
      <Data id={id} />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

【讨论】:

    【解决方案3】:

    您只需添加 params.id 作为依赖项。

    【讨论】:

      【解决方案4】:

      不要在你在 useEffect 中调用的函数内部调度,而是调用另一个 useEffect 来调度

       const [yourData, setyourData] = useState({});
      
        useEffect(() => {
          GetYourData();
        }, []);
      
        useEffect(() => {
          if (yourData) {
            //call dispatch action 
            dispatch(setDatatoRedux(yourData));
          }
        }, [yourData]);
      
      
        const GetYourData= () => {
          fetch('https://reactnative.dev/movies.json')
             .then((response) => response.json())
             .then((json) => {
                  if (result?.success == 1) {
                    setyourData(result);
                  }
             })
          .catch((error) => {
            console.error(error);
          });
        };
      

      【讨论】:

        猜你喜欢
        • 2017-09-20
        • 2016-01-02
        • 2017-04-19
        • 1970-01-01
        • 2016-09-05
        • 2021-01-02
        • 2019-02-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多