Formik/Yup/MUI表单文本框带默认值时如何启用提交按钮
问题原因
你直接给TextfieldWrapper传入value属性的写法,只是给MUI输入框DOM设置了显示值,完全没有同步到Formik的内部状态中。Formik存储的batchNo字段值始终是initialValues里的空字符串,Yup校验自然一直提示必填错误,isValid也会一直返回false。
另外你自定义组件里写的...disabled属于未定义变量,运行时会直接抛出引用错误,这个笔误也要先修复。
修复方案
核心原则:所有表单字段的值必须纳入Formik状态管理,禁止直接给输入框硬传value绕开Formik逻辑。
- 优先在
initialValues阶段就把children作为batchNo的初始值,这是最符合Formik设计逻辑的写法。 - 如果
children是异步传入、初始化时拿不到,就在自定义输入框组件挂载时,通过Formik提供的useField暴露的setValue方法把值写入Formik状态。 - 配置初始校验触发逻辑,保证页面加载时就能算出正确的
isValid状态。 - 调整提交按钮的禁用逻辑,适配默认值场景。
修正后的自定义输入框组件
import React from 'react'; import { TextField } from '@mui/material'; import { useField } from 'formik'; const TextfieldWrapper = ({ name, children, disabled, ...otherProps }) => { const [field, meta, helpers] = useField(name); // 挂载/children更新时,将传入的默认值同步到Formik状态 React.useEffect(() => { if (children !== undefined && children !== null) { // 第二个参数传false表示设置值后不标记为用户修改、不额外触发校验 helpers.setValue(children, false); } }, [children, helpers]); const configTextfield = { ...field, disabled: disabled || false, ...otherProps, fullWidth: true, variant: 'outlined', }; if (meta.touched && meta.error) { configTextfield.error = true; configTextfield.helperText = meta.error; } return <TextField {...configTextfield} />; };
修正后的表单代码
const valSchema = Yup.object().shape({ batchNo: Yup.string().required('Required'), item: Yup.string().required('Required'), price: Yup.string().required('Required'), }); const ItemForm = ({ children }) => { // 初始化时直接把children设为batchNo默认值 const initialValues = { batchNo: children || '', item: '', price: '' } return ( <Grid container> <Grid item xs={12}> <Container maxWidth="md"> <div> <Formik initialValues={initialValues} validationSchema={valSchema} // 有默认值时初始就标记字段为已接触,直接触发校验返回正确的isValid状态 initialTouched={{batchNo: !!children}} onSubmit={(values) => { localHandleSubmit(values); }} > {({ dirty, isValid, errors }) => ( <Form> <Grid container spacing={2}> <Grid item xs={12}> {/* 不需要额外写三元分支传value,组件内部自动处理children默认值 */} <TextfieldWrapper name="batchNo" label="Batch No" /> </Grid> <Grid item xs={6}> <TextfieldWrapper name="item" label="Item" /> </Grid> <Grid item xs={6}> <TextfieldWrapper name="price" label="Price" /> </Grid> <Grid item xs={6}> <Typography>{dirty ? 'true' : 'false'}</Typography> <Typography>{isValid ? 'true' : 'false'}</Typography> <Typography>{errors.batchNo}</Typography> </Grid> <Grid item xs={12}> <Button fullWidth variant="contained" // 去掉!dirty判断:如果业务允许直接提交带默认值的表单,不需要等用户修改内容 disabled={!isValid} type="submit" > ADD </Button> </Grid> </Grid> </Form> )} </Formik> </div> </Container> </Grid> </Grid> ); };
关键说明
- 直接给输入框传
value属于DOM层面的显式赋值,不会触发Formik的状态更新和校验逻辑,所有表单值的修改都要通过Formik暴露的field.onChange或者helpers.setValue方法完成。 setValue第二个参数传false是为了区分默认值和用户手动输入的值,避免默认值场景下错误触发dirty标记。- 如果业务要求必须用户修改过表单才能提交,保留按钮上的
!dirty判断即可;如果允许直接提交带默认值的表单,删掉该判断。
内容的提问来源于stack exchange,提问作者0x55b1E06FF
相关产品推荐
相关产品推荐

