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

React中在useMultiStepForm外部访问currentStepIndex的报错解决

在Formik验证Schema中访问自定义Hook的currentStepIndex解决方法

问题原因

你遇到的Uncaught ReferenceError是变量声明顺序导致的暂时性死区问题:

  • useFormik的调用早于useMultiStepForm,此时currentStepIndex还未被Hook初始化就尝试引用,触发了声明前访问的错误。
  • isLastStep能在onSubmit中正常使用,是因为onSubmit是回调函数,只有在表单提交时才执行,此时currentStepIndex已经完成初始化。

解决方案

以下两种方法都可以解决这个问题,根据你的需求选择:

方案一:使用useRef同步最新步骤值

利用useRef不受变量声明顺序限制的特性,同步currentStepIndex的最新值,让验证Schema能动态读取当前步骤:

修改NewRecord组件

import { useRef, useEffect } from "react";
import { useFormik } from "formik";
import { useMultiStepForm } from "./useMultiStepForm";
import { recordSchema } from "./recordSchema";
// 导入其他组件...

const NewRecord = () => {
  // 创建ref存储最新步骤索引
  const currentStepRef = useRef(0);

  const formik = useFormik({
    initialValues: {
      houseHolderName: "",
      houseHolderNo: "",
      email: "",
      address: "",
      phone: "",
      numberOfFamilyMembers: "2",
      familyMembersDetails: [],
      waterAvialablity: true,
      powerAvailability: true,
      owenership: true,
    },
    // 传入ref给验证Schema
    validationSchema: recordSchema(currentStepRef),
    onSubmit: (data) => {
      if (!isLastStep) return next();
      // 调用API逻辑
    },
    enableReinitialize: true, // 开启重新初始化,确保Schema动态更新
  });

  const handleAddFamilyMember = () => {
    formik.setFieldValue(
      "familyMembersDetails",
      [
        ...Array(parseInt(formik.values.numberOfFamilyMembers)).fill({
          fullName: "",
          dateOfBirth: "",
          nic: "",
          passport: "",
          advancedLevel: "",
          ordinaryLevel: "",
          higherStudies: "",
          gender: "",
          profession: "",
        }),
      ],
      true
    );
  };

  const { currentStepIndex, step, next, previous, isFirstStep, isLastStep } =
    useMultiStepForm([
      <FamilyDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <FamilyMembersDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
        handleAddFamilyMember={handleAddFamilyMember}
      />,
      <FamilyMemberDiseaseDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
        handleAddFamilyMember={handleAddFamilyMember}
      />,
      <HomeDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <VehicleDetails
        values={{ ...formik.values }}
        updateField={formik.handleChange}
        errors={{ ...formik.errors }}
      />,
      <Summary values={{ ...formik.values }} />,
    ]);

  // 同步最新步骤索引到ref
  useEffect(() => {
    currentStepRef.current = currentStepIndex;
  }, [currentStepIndex]);

  return (
    <NewRecordContainer>
      <div>
        <StepIndicator
          labels={["Family", "Members", "Deices", "Home", "Vehicle", "Summary"]}
          currentStepIndex={currentStepIndex}
        />
        <form action="#" onSubmit={formik.handleSubmit}>
          <div style={{ minHeight: 350 }}>{step}</div>
          <ControllerButtons>
            <button
              onClick={previous}
              type="button"
              className="btn btn-secondary"
              style={{ opacity: isFirstStep ? ".6" : "1" }}
            >
              Back
            </button>
            <button type="submit" className="btn btn-primary">
              {isLastStep ? "Submit" : "Next"}
            </button>
          </ControllerButtons>
        </form>
      </div>
    </NewRecordContainer>
  );
};

export default NewRecord;

修改recordSchema

import * as Yup from "yup";

export const recordSchema = (stepRef) => {
  return Yup.lazy((values) => {
    // 读取ref中的最新步骤值
    const step = stepRef.current;
    if (step === 2) {
      return Yup.object({
        familyMembersDetails: Yup.array()
          .of(
            Yup.object({
              fullName: Yup.string().required("Full Name is required"),
              dateOfBirth: Yup.string().required("Date of birth is required"),
              nic: Yup.string(),
              passport: Yup.string(),
              advancedLevel: Yup.string(),
              ordinaryLevel: Yup.string(),
              higherStudies: Yup.string(),
              gender: Yup.string().required("Select Gender"),
              profession: Yup.string(),
            })
          )
          .required("Family members details are required"),
      });
    } else {
      return Yup.object({
        houseHolderName: Yup.string()
          .max(25, "Must be 25 characters or less")
          .required("House Holder Name is required"),
        email: Yup.string()
          .email("Invalid email address")
          .required("Email is required"),
        address: Yup.string().required("Address is required"),
        phone: Yup.string().required("Phone number is required"),
        houseHolderNo: Yup.string().required(
          "House Holder Number is required."
        ),
        numberOfFamilyMembers: Yup.string().required(
          "Number of family member is required"
        ),
      });
    }
  });
};

方案二:用State同步步骤值+Formik重新初始化

通过useState同步步骤索引,配合Formik的enableReinitialize选项,动态更新验证Schema:

修改NewRecord组件核心部分

import { useState, useEffect } from "react";
import { useFormik } from "formik";

const NewRecord = () => {
  // 用state存储当前步骤,初始值为0
  const [currentStep, setCurrentStep] = useState(0);

  const formik = useFormik({
    initialValues: { /* 初始值不变 */ },
    validationSchema: recordSchema(currentStep),
    onSubmit: (data) => { /* 提交逻辑不变 */ },
    enableReinitialize: true, // 必须开启,让Formik在state变化时重新初始化
  });

  // 其他逻辑不变...

  const { currentStepIndex, step, next, previous, isFirstStep, isLastStep } =
    useMultiStepForm([/* 步骤组件不变 */]);

  // 步骤变化时更新state,触发Formik重新计算Schema
  useEffect(() => {
    setCurrentStep(currentStepIndex);
  }, [currentStepIndex]);

  // 返回部分不变...
};

此方案无需修改recordSchema原代码,因为它直接接收步骤值作为参数。

两种方案对比

  • 方案一更适合需要实时读取最新步骤值的场景,无需触发Formik重新初始化,性能更优。
  • 方案二更直观,代码改动更少,适合对性能要求不高的表单场景。

内容的提问来源于stack exchange,提问作者Mohamed Rifkan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 16:42:51