redux操作中查询api时的无限循环



我正试图通过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来调度一个redux thunk操作,该操作返回一个firebase数据快照。然后,一旦数据被加载,我就使用useAnswersState钩子来访问它。我正试图在实际视图组件的useEffect中调度我的查询,然后使用状态挂钩显示数据。

然而,当我尝试打印answers的值时,我从上面得到了错误。我非常感谢任何帮助,如果有帮助的话,我很乐意提供更多信息。然而,我已经测试了我的减速器和动作本身,这两个都按预期工作,所以我认为问题出在上述文件中。

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

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

不要在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);
});
};

尝试重构动作创建者,以便在效果内调用dispatch。您需要使调度取决于效果发射。

参见相关

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

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

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

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

要回答您的问题,如果您希望它处理多个分派,请将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);

最新更新