You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

useFormik结合useQuery获取初始值时触发React Hooks调用顺序错误

问题根源

你遇到的Hooks调用顺序错误,核心原因是违反了React Hooks的规则:所有Hooks必须在组件的顶层作用域调用,不能放在条件判断、循环或者提前return的语句之后。

原代码中,useFormik、useKeyboardControl、useAskBeforeUnload、useEffect这些Hooks调用前,存在多个提前return的分支(比如加载状态判断、数据不存在时的return),这会导致某些渲染周期里这些Hooks未被执行,React无法保证Hooks调用顺序一致,从而抛出错误。

解决步骤
  1. 将所有Hooks移到组件最顶部:确保每次渲染时,所有Hooks都按固定顺序被调用。
  2. 处理Formik的异步初始值:requestDetail是通过useQuery异步获取的,初始值可能为undefined,需要给Formik设置合理的默认初始值,或开启enableReinitialize支持初始值动态更新。
  3. 调整渲染逻辑:把加载、数据校验的条件判断放在所有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 09:25:35