javascript - 在 redux 操作中查询 api 时出现无限循环

标签 javascript reactjs firebase redux react-redux

我正在尝试通过 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 中收到该快照。我使用钩子(Hook)来调度我的操作:

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
}

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

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 调度一个 redux-thunk 操作,该操作返回 Firebase 数据快照。然后,我在加载数据后使用 useAnswersState Hook 来访问数据。我正在尝试在实际 View 组件的 useEffect 中分派(dispatch)查询,然后使用状态 Hook 显示数据。

但是,当我尝试打印 answers 的值时,我收到上面的错误。我将非常感谢任何帮助,并且很乐意提供更多信息(如果有帮助的话),但是,我已经测试了我的 reducer 和操作本身,两者都按预期工作,所以我相信问题出在文件中如上所述。

最佳答案

正如评论的那样;我认为无限循环的实际代码依赖于 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>

关于javascript - 在 redux 操作中查询 api 时出现无限循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60749896/

相关文章:

javascript - 如何在javascript中使视频仅在单击按钮时播放

java - Android 2.3 无法在未调用 Looper.prepare() (AsyncTask) 的线程内创建处理程序

javascript - Tab-navigation v5 标题中特定选项卡屏幕的渲染按钮

swift - Firebase 身份验证获取用户个人资料

ios - 谷歌云功能通知 iOS

javascript - Angular : How do I Rxjs filter by property through an array of objects?

javascript - 如何从 Node JS 数组中删除某些电子邮件地址

javascript - 将值传递给 jQuery 成功函数中的 Bootstrap 模式

javascript - react : Event-triggered Ajax call in componentDidUpdate or render creates infinite loop

javascript - 页面刷新时 ContextProvider 不挂载