Formik结合Yup实现表单Select字段验证问题求助
Formik中Select字段Yup验证失效的解决方案
问题描述
使用Formik构建的表单包含文本输入框与Select下拉框,通过Yup进行表单验证时,文本字段验证正常,但Select字段无法触发验证。
问题分析
从提供的代码来看,导致Select验证失效的核心原因有三点:
- 初始值未正确初始化:
mapPropsToValues返回的ulbListForm中,Select对应的字段(如state、district)初始值可能为undefined,Yup.object().required()对undefined的判断逻辑无法触发验证提示。 - 自定义FormController未关联Formik验证状态:FormController组件未接收Formik的
errors和touched属性,无法在界面上展示验证错误信息。 - 值类型与验证规则不匹配:若Select组件实际返回的是字符串(如选项ID),但Yup验证规则写的是
Yup.object().required(),会导致验证逻辑完全不匹配。
解决方案
1. 修正初始值设置
在withFormik的mapPropsToValues中,为Select字段设置符合类型要求的默认值(对象类型设为空对象,字符串类型设为空字符串):
mapPropsToValues: (props) => { return { state: props?.ulbListForm?.state || {}, district: props?.ulbListForm?.district || {}, ulbCode: props?.ulbListForm?.ulbCode || "", ulbName: props?.ulbListForm?.ulbName || "", ulbCategory: props?.ulbListForm?.ulbCategory || {} }; },
2. 关联Formik验证状态到FormController
通过Formik的Field组件包裹Select类型的FormController,传递错误和触摸状态,确保验证提示能正常显示:
<Field name="state"> {({ field, form }) => ( <FormController control="select" label="State" {...field} error={form.touched.state && Boolean(form.errors.state)} helperText={form.touched.state && form.errors.state} options={props.stateDropdown.data || []} /> )} </Field>
其他Select字段按照此方式同步修改。
3. 匹配验证规则与实际值类型
根据Select组件实际返回的值类型调整Yup规则:
- 若返回对象:保持
Yup.object().required("Required") - 若返回字符串(如选项ID):改为
Yup.string().required("Required")
示例调整后的验证规则:
const validationSchema = Yup.object({ state: Yup.object().required("Required"), district: Yup.object().required("Required"), ulbCode: Yup.string().required("Required"), ulbName: Yup.string().required("Required"), ulbCategory: Yup.object().required("Required") });
修改后的完整表单代码
import { Button, Card, Container, Grid } from "@mui/material"; import { FormController, I18n } from "common/components"; import { Form, Field, withFormik } from "formik"; import React, { useEffect } from "react"; import { connect, useDispatch } from "react-redux"; import * as Yup from "yup"; import { ClearUlb, getAddUlbDetails, getUlbCategorySelect, getUlbDistrictSelect, getUlbStateSelect } from "../actions"; import { useParams } from "react-router-dom"; import { getCategoryDropdownSelect, getDistrictDropdownSelect, getStateDropdownSelect, getUlbListForm } from "../selectors"; import { actions as sliceActions } from "../slice"; import { createStructuredSelector } from "reselect"; const UlbMasterDetailsAdd = (props) => { const dispatch = useDispatch(); const { id = null } = useParams(); useEffect(() => { dispatch(getUlbStateSelect()); dispatch(getUlbDistrictSelect()); dispatch(getUlbCategorySelect()); if (id) { // dispatch(getUlbById(id)); } return () => dispatch(ClearUlb()); }, [id]); const containerStyles = { display: "flex", flexDirection: "column", justifyContent: "center", alignItem: "center", margin: "auto", padding: "100px", marginLeft: "100px", marginRight: "100px" }; const formStyle = { padding: "20px" }; return ( <Card sx={containerStyles}> <Container maxWidth="md"> <Form> <Grid container> <Grid item xs={6} sx={formStyle}> <Field name="state"> {({ field, form }) => ( <FormController control="select" label="State" {...field} error={form.touched.state && Boolean(form.errors.state)} helperText={form.touched.state && form.errors.state} options={props.stateDropdown.data || []} /> )} </Field> </Grid> <Grid item xs={6} sx={formStyle}> <Field name="district"> {({ field, form }) => ( <FormController control="select" label="District" {...field} error={form.touched.district && Boolean(form.errors.district)} helperText={form.touched.district && form.errors.district} options={props.selectDistrict.data || []} /> )} </Field> </Grid> <Grid item xs={6} sx={formStyle}> <FormController control="input" type="text" label="ULB Code" name="ulbCode" /> </Grid> <Grid item xs={6} sx={formStyle}> <FormController control="input" type="text" label="ULB Name" name="ulbName" /> </Grid> <Grid item xs={6} sx={formStyle}> <Field name="ulbCategory"> {({ field, form }) => ( <FormController control="select" label="ULB Category" {...field} error={form.touched.ulbCategory && Boolean(form.errors.ulbCategory)} helperText={form.touched.ulbCategory && form.errors.ulbCategory} options={props.selectCategory.data || []} /> )} </Field> </Grid> </Grid> <Grid sx={{ display: "flex", justifyContent: "center", padding: "20px" }}> {id && <Button variant="contained" type="submit" > {I18n("update")} </Button>} {!id && <Button variant="contained" type="submit" > {I18n("submit")} </Button>} </Grid> </Form> </Container> </Card> ); }; const mapStateToProps = createStructuredSelector({ ulbListForm: getUlbListForm, stateDropdown: getStateDropdownSelect, selectDistrict: getDistrictDropdownSelect, selectCategory: getCategoryDropdownSelect }); const mapDispatchToProps = (dispatch) => ({ setStoredValue: (data) => dispatch(sliceActions.setUlbListFormData(data)), submitValue: (data) => dispatch(getAddUlbDetails(data)) }); const validationSchema = Yup.object({ state: Yup.object().required("Required"), district: Yup.object().required("Required"), ulbCode: Yup.string().required("Required"), ulbName: Yup.string().required("Required"), ulbCategory: Yup.object().required("Required") }); const addUlbList = withFormik({ validationSchema, enableReinitialize: true, mapPropsToValues: (props) => { return { state: props?.ulbListForm?.state || {}, district: props?.ulbListForm?.district || {}, ulbCode: props?.ulbListForm?.ulbCode || "", ulbName: props?.ulbListForm?.ulbName || "", ulbCategory: props?.ulbListForm?.ulbCategory || {} }; }, handleSubmit: (values, { props }) => { props.submitValue(values); }, displayName: "addUlbList" })(UlbMasterDetailsAdd); export default connect(mapStateToProps, mapDispatchToProps)(addUlbList);
补充说明
如果自定义的FormController本身未实现error和helperText的渲染逻辑,需要在FormController内部添加错误提示的展示代码,确保验证失败时用户能看到错误信息。
内容的提问来源于stack exchange,提问作者Arya S Anil
相关产品推荐
相关产品推荐

