我正在尝试通过 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/