多步骤表单中Formik与Yup校验问题:按钮状态控制异常
解决多步骤Formik表单的按钮禁用与分步校验问题
看起来你遇到的核心问题有两个:一是按钮禁用逻辑搞反了,二是多步骤场景下Formik默认的全局校验不适用——毕竟你只需要校验当前步骤的字段,而不是所有未填写的后续字段。我来一步步帮你搞定:
1. 先修正按钮禁用的逻辑误区
你现在写的disabled={isValid}完全搞反了需求:你想要校验通过时启用按钮,也就是当校验有效时按钮可点击,所以逻辑应该是disabled={!校验有效状态}。但别急,多步骤场景下全局的isValid会因为未填写的后续字段一直为false,所以我们得先解决分步校验的问题。
2. 实现分步字段校验
首先给每个步骤映射对应的需要校验的字段,这样我们只需要检查当前步骤的字段是否符合规则:
// 新增:定义每个步骤对应的校验字段 const stepFieldMap = [ ['kg'], // 第1步对应字段 ['lastName'], // 第2步对应字段 ['email'], // 第3步对应字段 [] // 第4步如果是确认页,可设为空数组 ];
然后在Formik的回调中,计算当前步骤的字段是否都通过校验,替代全局的isValid:
// 在Formik的render回调中添加以下逻辑 const currentFields = stepFieldMap[currentStep]; // 检查当前步骤的字段是否都满足校验要求 const isCurrentStepValid = currentFields.every(field => { // 字段没有错误,且内容长度≥2(满足min要求),或者已经被用户触碰过(避免初始空值误判) return !errors[field] && (values[field].length >= 2 || touched[field]); }); // 按钮禁用条件:当前步骤校验不通过,或者初始无任何输入 const isNextDisabled = !isCurrentStepValid || (!isDirty && currentFields.some(f => values[f] === ''));
3. 整合完整代码
把上面的逻辑整合到你的代码中,同时区分中间步骤的Next按钮和最后一步的Submit按钮,避免提前触发表单提交:
const pages = [<Page1 />, <Page2 />, <Page3 />, <Page4 />]; // 新增步骤字段映射 const stepFieldMap = [ ['kg'], ['lastName'], ['email'], [] ]; const SignupSchema = Yup.object().shape({ kg: Yup.string() .min(2, "Too Short!") .max(50, "Too Long!") .required("Required"), lastName: Yup.string() .min(2, "Too Short!") .max(50, "Too Long!") .required("Required"), email: Yup.string() .email("Invalid email") .required("Required") }); const SignUp = () => { const currentStep = useSelector((state: RootState) => state); const dispatch = useDispatch(); return ( <> <Logo /> <div className="signup-container"> <Formik initialValues={{ kg: "", lastName: "", email: "" }} onSubmit={values => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); }, 500); }} validationSchema={SignupSchema} > {({ errors, touched, values, isDirty }) => { const currentFields = stepFieldMap[currentStep]; const isCurrentStepValid = currentFields.every(field => { return !errors[field] && (values[field].length >= 2 || touched[field]); }); const isNextDisabled = !isCurrentStepValid || (!isDirty && currentFields.some(f => values[f] === '')); return ( <Form> <LinearProgress variant="determinate" /> <div className="questions">{pages[currentStep]}</div> <button type="button" onClick={() => dispatch(decrement())} disabled={currentStep === 0} // 第一步禁用上一步按钮 > Prev </button> {/* 区分最后一步的提交按钮和中间步骤的下一步按钮 */} {currentStep === pages.length - 1 ? ( <button type="submit" disabled={!isDirty || !isCurrentStepValid}> Submit </button> ) : ( <button type="button" disabled={isNextDisabled} onClick={() => dispatch(increment())} > Next </button> )} </Form> ); }} </Formik> </div> </> ); };
关键说明
- 分步校验:通过
stepFieldMap关联步骤和字段,只检查当前步骤的字段规则,避免未填写的后续字段导致全局校验失败。 - 禁用逻辑精准控制:
isNextDisabled结合了当前步骤的校验结果和输入状态,确保初始空值时按钮禁用,只有满足字段长度要求且无错误时才启用。 - 按钮角色区分:最后一步显示Submit按钮触发表单提交,中间步骤用普通按钮切换步骤,符合多步骤表单的交互逻辑。
内容的提问来源于stack exchange,提问作者Gim-Cojocaru Raul-Cristian
相关产品推荐
相关产品推荐

