Yup的notRequired在单选框与URL字段上失效问题
表单验证问题:Radio非必填字段仍强制校验的解决方案
问题背景
使用React-Bootstrap、React-Hook-Form和Yup构建表单,其中offerInternships、offerVisaSponsorship、offerRemoteWork三个Radio字段已在Yup Schema中标记为notRequired(),但提交表单时仍被提示必须填写;URL字段已通过在正则末尾添加|^$解决空值校验问题,但Radio字段的问题依旧存在。
问题原因
- 默认值未同步到表单状态:Yup的
default(false)不会自动同步到React-Hook-Form的表单状态中,用户未选择Radio选项时,字段值为undefined,触发Yup隐性类型校验。 - 类型不匹配:Radio组件的
value属性设置为字符串"true"/"false",但Yup Schema中定义的是bool类型,类型不匹配导致校验失败,即使标记为非必填也会报错。
修复步骤
1. 为React-Hook-Form设置默认值
在useForm配置中添加defaultValues,确保Radio字段初始值为布尔类型的false:
const { register, handleSubmit, watch, formState: { errors }, } = useForm({ resolver: yupResolver(formSchema), mode: "all", defaultValues: { // 添加默认值配置 offerInternships: false, offerRemoteWork: false, offerVisaSponsorship: false, } });
2. 调整Yup Schema的类型校验
将Radio字段的类型从yup.bool()改为yup.mixed(),允许接受字符串并转换为布尔值,同时保留非必填和默认值设置:
const formSchema = yup.object().shape({ // ...其他字段保持不变 offerInternships: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), offerRemoteWork: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), offerVisaSponsorship: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), // ...其他字段保持不变 });
3. 移除Register中的冗余配置
删除Radio组件register中的{ required: false },因为已在Yup Schema中定义非必填,重复设置可能导致冲突:
// 原代码 <Form.Check inline label="Yes" value="true" {...register("offerInternships", { required: false })} type="radio" isInvalid={!!errors.offerInternships} /> // 修改后 <Form.Check inline label="Yes" value="true" {...register("offerInternships")} type="radio" isInvalid={!!errors.offerInternships} />
修改后的完整代码
import { Container, Row, Col, Button, Form } from "react-bootstrap"; import * as yup from "yup"; import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; /** * Company's validation schema. */ const re = /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$|^$/gm; const formSchema = yup.object().shape({ url: yup.string().matches(re, "Enter a valid URL!").required(), name: yup.string().required(), location: yup.object().shape({ country: yup.string().required(), state: yup.string().required(), city: yup.string().required(), address: yup.string().required(), }), offerInternships: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), offerRemoteWork: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), offerVisaSponsorship: yup.mixed().oneOf([true, false, 'true', 'false']) .transform(val => val === 'true' || val === true) .default(false) .notRequired(), socialMedia: yup.object().shape({ Linkedin: yup.string().matches(re, "Enter a valid URL!").notRequired(), GitHub: yup.string().matches(re, "Enter a valid URL!").notRequired(), }), }); const RequestForm = () => { const { register, handleSubmit, watch, formState: { errors }, } = useForm({ resolver: yupResolver(formSchema), mode: "all", defaultValues: { offerInternships: false, offerRemoteWork: false, offerVisaSponsorship: false, } }); /** * Handling the submittion of the form */ const onSubmit = async (data) => { try { console.log(errors); console.log(data); } catch (error) { console.log(error); } }; return ( <> <Container id="RequestForm-UserForm" className="mt-5 outer-border p-5"> <Form onSubmit={handleSubmit(onSubmit)}> <Row id="company-details-row"> <Col id="company-details-row-input" sm={12} md={9}> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>Company's name</Form.Label> <Form.Control type="text" placeholder="e.g. Microsoft" {...register("name")} isInvalid={!!errors?.name} /> </Form.Group> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>Company's website</Form.Label> <Form.Control type="text" placeholder="e.g. https://www.microsoft.com" {...register("url")} isInvalid={!!errors.url} /> </Form.Group> <h3 className="mb-0">Location</h3> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>Country</Form.Label> <Form.Control type="text" placeholder="e.g. Egypt, United States of America,...etc." {...register("location.country")} isInvalid={!!errors.location?.country} /> </Form.Group> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>State/Governorate</Form.Label> <Form.Control type="text" placeholder="e.g. Cairo, California, Berlin...etc." {...register("location.state")} isInvalid={!!errors.location?.state} /> </Form.Group> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>City</Form.Label> <Form.Control type="text" placeholder="e.g. Maadi, San Francisco,...etc." {...register("location.city")} isInvalid={!!errors.location?.city} /> </Form.Group> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label>Address</Form.Label> <Form.Control type="text" placeholder="e.g. 14th Zaki Sayed St." {...register("location.address")} isInvalid={!!errors.location?.address} /> </Form.Group> <h3 className="mt-2 mb-0">Social Media</h3> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label> <i className="icon ion-social-linkedin"></i> Linkedin </Form.Label> <Form.Control type="text" placeholder="e.g. https://www.linkedin.com/company/x" {...register("socialMedia.Linkedin")} isInvalid={!!errors.socialMedia?.Linkedin} /> </Form.Group> <Form.Group className="mb-2" controlId="formBasicEmail"> <Form.Label> <i className="icon ion-social-github"></i> GitHub </Form.Label> <Form.Control type="text" placeholder="e.g. https://www.github.com/company" {...register("socialMedia.GitHub")} isInvalid={!!errors.socialMedia?.GitHub} /> </Form.Group> <h3 className="mt-2 mb-0">Culture & Environment</h3> <div id="offer-Internship" key="inline-radio-internships"> <Form.Label>Does your company offer internships? 🎓</Form.Label> <br></br> <Form.Check inline label="Yes" value="true" {...register("offerInternships")} type="radio" isInvalid={!!errors.offerInternships} /> <Form.Check inline label="No" value="false" {...register("offerInternships")} type="radio" isInvalid={!!errors.offerInternships} /> </div> <div id="offer-VisaSponsorship" key="inline-radio-visa"> <Form.Label> Does your company offer visa sponsorship? ✈️ </Form.Label> <br></br> <Form.Check inline label="Yes" value="true" {...register("offerVisaSponsorship")} type="radio" isInvalid={!!errors.offerVisaSponsorship} /> <Form.Check {...register("offerVisaSponsorship")} inline label="No" value="false" type="radio" isInvalid={!!errors.offerVisaSponsorship} /> </div> <div id="offer-remoteWork" key="inline-radio-remote"> <Form.Label> Does your company offer remote working? 🗺️ </Form.Label> <br></br> <Form.Check {...register("offerRemoteWork")} inline label="Yes" value="true" type="radio" isInvalid={!!errors.offerRemoteWork} /> <Form.Check {...register("offerRemoteWork")} inline label="No" value="false" type="radio" isInvalid={!!errors.offerRemoteWork} /> </div> </Col> </Row> <Row id="company-submit text-center"> <Col className="text-center" sm={12}> <h2 className="fs-1 main-color-1-dark"> We have reached to the end of the form! 🎆 </h2> <Button variant="success" type="submit" className="rubik-font"> Submit form </Button> </Col> </Row> </Form> </Container> </> ); }; export default RequestForm;
内容的提问来源于stack exchange,提问作者miserylord
相关产品推荐
相关产品推荐

