useFormik结合useQuery获取初始值时触发React Hooks调用顺序错误
问题根源
你遇到的Hooks调用顺序错误,核心原因是违反了React Hooks的规则:所有Hooks必须在组件的顶层作用域调用,不能放在条件判断、循环或者提前return的语句之后。
原代码中,useFormik、useKeyboardControl、useAskBeforeUnload、useEffect这些Hooks调用前,存在多个提前return的分支(比如加载状态判断、数据不存在时的return),这会导致某些渲染周期里这些Hooks未被执行,React无法保证Hooks调用顺序一致,从而抛出错误。
解决步骤
- 将所有Hooks移到组件最顶部:确保每次渲染时,所有Hooks都按固定顺序被调用。
- 处理Formik的异步初始值:
requestDetail是通过useQuery异步获取的,初始值可能为undefined,需要给Formik设置合理的默认初始值,或开启enableReinitialize支持初始值动态更新。 - 调整渲染逻辑:把加载、数据校验的条件判断放在所有Hooks调用之后。
修改后的代码示例
const Detail: FC<Props> = ({ requestId }) => { // 所有Hooks必须放在组件最顶层,不能在任何条件判断之后 const [isSaveButtonVisible, setIsSaveButtonVisible] = useState<boolean>(false); const { openModal, closeModal } = useModal(); const { isLoading: isButtonsLoading, data: buttonsResponse, refetch: buttonsRefetch, } = useQuery( `request-buttons-${requestId}`, () => getRequestActionButtons(requestId), queryConfig ); const { isLoading: detailIsLoading, data: detailResponse, refetch: detailRefetch, } = useQuery( `request-detail-${requestId}`, () => getRequestDetail(requestId), queryConfig ); const requestDetail = useMemo(() => detailResponse, [detailResponse]); const requestActionButtons = useMemo(() => buttonsResponse?.flowActions, [buttonsResponse]); // 提前定义Formik,设置默认初始值和enableReinitialize const requestDetailFormik = useFormik({ initialValues: requestDetail || {}, // 给默认空对象避免undefined enableReinitialize: true, // 当requestDetail更新时自动重新初始化Formik onSubmit: async (values) => { // 你的提交逻辑 }, }); const handleRefetchDetailAsync = async () => { const updatedDetailRes = await detailRefetch(); await buttonsRefetch(); requestDetailFormik.resetForm({ values: updatedDetailRes.data }); }; useKeyboardControl(() => { if (!isSaveButtonVisible) return; requestDetailFormik.handleSubmit(); }, "s"); useAskBeforeUnload(isSaveButtonVisible); useEffect(() => { if (requestDetail) { const isDifferent = checkIfDifferentOnForm(requestDetail, requestDetailFormik.values); setIsSaveButtonVisible(isDifferent); } }, [requestDetail, requestDetailFormik.values]); // 所有渲染条件判断放在Hooks之后 if (detailIsLoading || isButtonsLoading) { return <RequestDetailSkeleton />; } if (!requestDetail || !requestActionButtons) { return null; // 返回null更符合React规范 } // 这里渲染你的表单内容 return ( // ... 表单组件,使用requestDetailFormik的props ); };
关键说明
enableReinitialize: true:让Formik在initialValues变化时自动重新初始化表单,适配异步数据加载场景。- 所有Hooks必须在组件顶层调用:哪怕某些Hooks的逻辑依赖后续数据,也不能放在条件判断之后,而是在Hook内部处理空值情况(比如
useEffect里先判断requestDetail是否存在)。 - 避免提前return打断Hooks调用:把所有需要提前退出的渲染逻辑,都放在所有Hooks调用完成之后执行。
内容的提问来源于stack exchange,提问作者Onuralp
相关产品推荐
相关产品推荐

