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
相关产品推荐
相关产品推荐

