Formik添加appleMusicId时意外重置timeDescription等字段值求助
Formik表单字段值重置问题排查与修复
问题描述
使用Formik结合本地song状态开发表单时,点击"Add Apple ID"按钮添加appleMusicId字段值后,timeDescription和sceneDescription字段值会被重置。首次提交时这两个字段有正常值,操作后再次提交,这两个字段值消失,仅保留其他字段内容。
相关代码
主组件代码
import { TextField } from "@/components/TextField"; import { Button } from "@chakra-ui/react"; import { Formik, Form } from "formik"; import { validate } from "graphql"; import { useState } from "react"; const initialValues = { name: "", artistName: "", episodeId: undefined, movieId: undefined, spotifyId: "", appleMusicId: "", sceneDescription: "", timeDescription: undefined, }; const AddSong = () => { const [song, setSong] = useState(initialValues); const submit = (values: any) => { console.log(values, "values"); } const addAppleId = () => { setSong((v) => { return { ...v, appleMusicId: "dummyId", }; }); }; return ( <Formik initialValues={song} onSubmit={(values) => submit(values)} validationSchema={validate} enableReinitialize={true} > <Form> <TextField name={"name"} label={"Name"} type={"text"} /> <TextField name={"artistName"} label={"Artist Name"} type={"text"} /> <TextField name={"timeDescription"} label={"Time Description"} type={"number"} placeholder="How many minutes in, was this song played?" /> <TextField name={"sceneDescription"} label={"Scene Description"} type={"text"} /> <Button onClick={addAppleId}>Test Add Apple Id</Button> <Button isLoading={isLoading} type={"submit"}> Create </Button> </Form> </Formik> ) };
TextField组件代码
export const TextField = ({ label, value, ...props }: TextFieldProps) => { const [field, meta] = useField(props); return ( <FormControl isInvalid={ meta.touched && (meta.error && meta.error?.length > 0 ? true : false) } mb={3} > <FormLabel aria-label={field.name} htmlFor={field.name} mb={0} textColor={useColorModeValue('gray.700', 'gray.100')} > {label} </FormLabel> <Input {...field} {...props} value={value ? value : undefined} autoComplete="off" borderRadius={0} paddingLeft={2} border={"2px solid"} borderColor={"gray.200"} _invalid={{ borderColor: "error-red" }} /> <ErrorMessage name={field.name} component="span" className="text-sm pt-2 text-red-error" /> <Text pt={1} fontSize={'sm'} color={useColorModeValue('gray.400', 'gray.600')}>{props.footerText}</Text> </FormControl> ); };
原因分析
- TextField组件手动覆盖value导致状态同步异常:在TextField的Input组件中,手动设置
value={value ? value : undefined},打断了Formik的状态管理逻辑。当组件未从props传入value时(如timeDescription和sceneDescription字段),Input的value被强制设为undefined,而非Formik管理的当前字段值。结合enableReinitialize={true},当外部song状态更新时,Formik重新初始化表单,这两个字段的原有值被覆盖为undefined。 - 外部状态与Formik状态双管理冲突:通过本地
song状态作为Formik的initialValues,同时用setSong修改字段值,再结合enableReinitialize,导致表单状态在外部状态和Formik内部状态之间反复同步,加剧了值丢失问题。
修复方案
方案1:修复TextField组件的value绑定
移除Input组件中手动设置的value属性,让Formik的field对象全权管理输入值,恢复Formik的状态同步能力:
export const TextField = ({ label, value, ...props }: TextFieldProps) => { const [field, meta] = useField(props); return ( <FormControl isInvalid={ meta.touched && (meta.error && meta.error?.length > 0 ? true : false) } mb={3} > <FormLabel aria-label={field.name} htmlFor={field.name} mb={0} textColor={useColorModeValue('gray.700', 'gray.100')} > {label} </FormLabel> <Input {...field} {...props} autoComplete="off" borderRadius={0} paddingLeft={2} border={"2px solid"} borderColor={"gray.200"} _invalid={{ borderColor: "error-red" }} /> <ErrorMessage name={field.name} component="span" className="text-sm pt-2 text-red-error" /> <Text pt={1} fontSize={'sm'} color={useColorModeValue('gray.400', 'gray.600')}>{props.footerText}</Text> </FormControl> ); };
方案2:移除外部状态,完全由Formik管理表单
如果不需要外部控制表单状态,可删除本地song状态,直接使用Formik的内部状态管理字段值,避免双状态冲突:
import { TextField } from "@/components/TextField"; import { Button } from "@chakra-ui/react"; import { Formik, Form, useFormikContext } from "formik"; import { validate } from "graphql"; const initialValues = { name: "", artistName: "", episodeId: undefined, movieId: undefined, spotifyId: "", appleMusicId: "", sceneDescription: "", timeDescription: undefined, }; const AddSong = () => { const submit = (values: any) => { console.log(values, "values"); }; const AddAppleIdButton = () => { const { setFieldValue } = useFormikContext(); const addAppleId = () => { setFieldValue('appleMusicId', 'dummyId'); }; return <Button onClick={addAppleId}>Test Add Apple Id</Button>; }; return ( <Formik initialValues={initialValues} onSubmit={(values) => submit(values)} validationSchema={validate} > <Form> <TextField name={"name"} label={"Name"} type={"text"} /> <TextField name={"artistName"} label={"Artist Name"} type={"text"} /> <TextField name={"timeDescription"} label={"Time Description"} type={"number"} placeholder="How many minutes in, was this song played?" /> <TextField name={"sceneDescription"} label={"Scene Description"} type={"text"} /> <AddAppleIdButton /> <Button isLoading={isLoading} type={"submit"}> Create </Button> </Form> </Formik> ) };
说明
方案1适用于需要外部状态控制表单的场景,方案2更符合Formik的最佳实践,让Formik全权管理表单状态,避免状态同步问题。
内容的提问来源于stack exchange,提问作者unicorn_surprise
相关产品推荐
相关产品推荐

