如何在React Hook Form中结合Yup实现单字段分步验证
实现React Hook Form分步验证(仅验证当前步骤字段)
针对你的测验组件需求,这里提供两种实用的分步验证方案,只验证当前渲染组件对应的字段:
方案一:动态生成验证Schema
通过维护当前步骤状态,每次只加载对应字段的Yup规则,让React Hook Form仅验证当前步骤的字段。
代码示例
import { useState } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; export const QUIZ_SCHEMA = yup.object().shape({ radioGroup: yup.number().required('Please select an option to proceed'), arrangedText: yup.array().length(5, 'Please answer the question to proceed'), essay: yup .string() .required('Answer is required') .min(2, 'Min 2 characters required') .max(1000, 'Max 1000 characters allowed'), }); const QuizComponent = () => { // 维护当前步骤,对应表单字段名 const [currentStep, setCurrentStep] = useState('radioGroup'); const { control, handleSubmit, formState: { errors, isValid }, reset, } = useForm({ defaultValues: { radioGroup: undefined, arrangedText: [], essay: '', }, // 动态生成当前步骤的验证Schema resolver: yupResolver( yup.object().shape({ [currentStep]: QUIZ_SCHEMA.fields[currentStep], }) ), }); const handleNext = () => { if (!isValid) return; // 切换步骤逻辑 switch (currentStep) { case 'radioGroup': setCurrentStep('arrangedText'); break; case 'arrangedText': setCurrentStep('essay'); break; case 'essay': handleSubmit((data) => { console.log('最终提交数据:', data); })(); break; default: break; } }; // 渲染当前步骤组件 const renderStepComponent = () => { switch (currentStep) { case 'radioGroup': return ( <div> <label>选择一个选项:</label> <Controller name="radioGroup" control={control} render={({ field }) => ( <> <input type="radio" {...field} value={1} /> 选项1 <input type="radio" {...field} value={2} /> 选项2 <input type="radio" {...field} value={3} /> 选项3 </> )} /> {errors.radioGroup && <p style={{color: 'red'}}>{errors.radioGroup.message}</p>} </div> ); case 'arrangedText': return ( <div> <label>排列5项文本(逗号分隔数字):</label> <Controller name="arrangedText" control={control} render={({ field }) => ( <input type="text" onChange={(e) => field.onChange(e.target.value.split(',').map(Number))} placeholder="例如:1,2,3,4,5" /> )} /> {errors.arrangedText && <p style={{color: 'red'}}>{errors.arrangedText.message}</p>} </div> ); case 'essay': return ( <div> <label>输入回答:</label> <Controller name="essay" control={control} render={({ field }) => ( <textarea {...field} rows={5} placeholder="至少输入2个字符" /> )} /> {errors.essay && <p style={{color: 'red'}}>{errors.essay.message}</p>} </div> ); default: return null; } }; return ( <div> {renderStepComponent()} <button onClick={handleNext} disabled={!isValid}> {currentStep === 'essay' ? '提交' : '下一步'} </button> </div> ); }; export default QuizComponent;
方案二:手动触发单个字段验证
保留完整的Yup Schema,通过trigger方法仅验证当前步骤对应的字段,适合需要最终提交时统一验证所有字段的场景。
代码示例
import { useState } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; export const QUIZ_SCHEMA = yup.object().shape({ radioGroup: yup.number().required('Please select an option to proceed'), arrangedText: yup.array().length(5, 'Please answer the question to proceed'), essay: yup .string() .required('Answer is required') .min(2, 'Min 2 characters required') .max(1000, 'Max 1000 characters allowed'), }); const QuizComponent = () => { const [currentStep, setCurrentStep] = useState('radioGroup'); const { control, handleSubmit, trigger, formState: { errors }, reset, } = useForm({ defaultValues: { radioGroup: undefined, arrangedText: [], essay: '', }, resolver: yupResolver(QUIZ_SCHEMA), }); const handleNext = async () => { // 仅触发当前步骤字段的验证 const isValid = await trigger(currentStep); if (!isValid) return; // 切换步骤或提交 switch (currentStep) { case 'radioGroup': setCurrentStep('arrangedText'); break; case 'arrangedText': setCurrentStep('essay'); break; case 'essay': handleSubmit((data) => { console.log('最终提交数据:', data); })(); break; default: break; } }; // 渲染当前步骤组件(同方案一逻辑) const renderStepComponent = () => { switch (currentStep) { case 'radioGroup': return ( <div> <label>选择一个选项:</label> <Controller name="radioGroup" control={control} render={({ field }) => ( <> <input type="radio" {...field} value={1} /> 选项1 <input type="radio" {...field} value={2} /> 选项2 <input type="radio" {...field} value={3} /> 选项3 </> )} /> {errors.radioGroup && <p style={{color: 'red'}}>{errors.radioGroup.message}</p>} </div> ); case 'arrangedText': return ( <div> <label>排列5项文本(逗号分隔数字):</label> <Controller name="arrangedText" control={control} render={({ field }) => ( <input type="text" onChange={(e) => field.onChange(e.target.value.split(',').map(Number))} placeholder="例如:1,2,3,4,5" /> )} /> {errors.arrangedText && <p style={{color: 'red'}}>{errors.arrangedText.message}</p>} </div> ); case 'essay': return ( <div> <label>输入回答:</label> <Controller name="essay" control={control} render={({ field }) => ( <textarea {...field} rows={5} placeholder="至少输入2个字符" /> )} /> {errors.essay && <p style={{color: 'red'}}>{errors.essay.message}</p>} </div> ); default: return null; } }; return ( <div> {renderStepComponent()} <button onClick={handleNext}> {currentStep === 'essay' ? '提交' : '下一步'} </button> </div> ); }; export default QuizComponent;
方案对比
- 动态Schema方案:全局
isValid会准确反映当前步骤的验证状态,适合用isValid禁用下一步按钮的场景,但每次切换步骤会重新初始化验证逻辑。 - 手动trigger方案:保留完整Schema,最终提交时会自动验证所有字段,灵活度更高,但需要手动处理验证触发,不依赖全局
isValid状态。
内容的提问来源于stack exchange,提问作者Keshav Khetan
相关产品推荐
相关产品推荐

