You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Formik+Ant Design表单输入随输入重渲染失焦问题求助

解决Formik + Ant Design Form.List输入框重渲染失焦问题

问题根源分析

  • 组件重复创建:FormInput定义在父组件内部,每次Formik状态更新时,父组件重渲染会重新创建FormInput组件,导致输入框被卸载后重新挂载,失去焦点。
  • 重复状态更新:FormInput中同时调用formik.handleChange和formik.setFieldValue,触发两次状态更新,加重重渲染。
  • 字段路径不匹配:手动拼接的Formik字段路径与Form.List的内部字段管理逻辑冲突,导致状态更新时触发全量列表重渲染。

修复步骤

1. 缓存FormInput组件

将FormInput用React.memo包裹,或者移到父组件外部,避免每次父组件重渲染都重新创建该组件:

const FormInput = React.memo(({ formik, name, placeholder, ...rest }) => (
    <div className="space-y-1 w-full pt-3 pr-2">
        <label className="font-bold text-sm text-secondary">
            Web Link
            <span className="text-red-500">*</span>
        </label>
        <Input
            {...formik.getFieldProps(name)}
            placeholder={placeholder}
            {...rest}
        />
    </div>
));

2. 移除重复的状态更新逻辑

formik.getFieldProps(name)已经内置了value和onChange逻辑,不需要手动设置value或重复调用状态更新方法,这会触发多余的重渲染。

3. 对齐Form.List与Formik的字段路径

直接使用Form.List提供的name参数拼接Formik字段路径,避免手动拼接导致的路径错误:

<Form.List name="websites">
    {(fields, { add, remove }) => {
        return (
            <div key="test">
                {fields.map(({ name, key, fieldKey, ...restField }) => {
                    return (
                        <div key={fieldKey}>
                            <Form.Item
                                {...restField}
                                name={[name, 'domain']}
                                fieldKey={[fieldKey, 'domain']}
                                validateTrigger={["onChange", "onBlur"]}
                                rules={[
                                    {
                                        required: true,
                                        message: 'Domain is required'
                                    }
                                ]}
                                noStyle
                                className="w-full"
                            >
                                <div className="flex items-end w-full">
                                    <FormInput
                                        formik={formik}
                                        name={`websites[${name}].domain`}
                                        placeholder="URL" 
                                    />
                                    {fields.length > 1 && (
                                        <Button
                                            type="default"
                                            size="small"
                                            icon={<LuTrash2 size={20}/>}
                                            onClick={() => {
                                                const deletedItem = formik.values.websites[name];
                                                if (deletedItem) {
                                                    // 函数式更新避免依赖旧状态
                                                    formik.setFieldValue('delete_websites', prev => [...prev, deletedItem]);
                                                    remove(name);
                                                }
                                            }}
                                            className="mb-1 bg-[#F06548] border-[#F06548] text-white flex items-center justify-center h-full"
                                        />
                                    )}
                                </div>
                            </Form.Item>
                        </div>
                    );
                })}
                <div className="mt-5 w-full">
                    <AntButton
                        type="dashed"
                        onClick={() => add({ domain: '' })}
                        icon={<PlusOutlined />}
                        className="w-full border-2 border-dashed border-gray-500"
                    >
                        Add Website
                    </AntButton>
                </div>
            </div>
        );
    }}
</Form.List>

4. 优化Formik初始化配置

确保websites数组的每个元素都包含domain字段,避免渲染异常:

const formik = useFormik({
    enableReinitialize: true,
    initialValues: {
        sp_id: data?.id,
        websites: (data?.websites || []).map(item => ({ domain: item.domain || '' })),
        create_websites: [],
        delete_websites: [],
        update_websites: []
    },
    validationSchema: Yup.object({
        sp_id: Yup.string().required("This field is required and can't be changed"),
        websites: Yup.array().of(Yup.object({
            domain: Yup.string().required('Domain is required')
        }))
    }),
    onSubmit: (values) => {
        console.log('submit:', values)
    }
});

额外优化建议

  • 使用formik.setFieldValue的函数式更新模式,避免依赖旧状态值,减少不必要的重渲染触发。
  • 如果父组件有频繁更新的props,用useMemo缓存initialValues,避免enableReinitialize触发不必要的表单重新初始化。

内容的提问来源于stack exchange,提问作者user23319726

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 09:54:53