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

useFormik与useState结合时输入框值无法更新的问题排查

问题

我在开发电商后台的分类创建表单,每个分类包含ParentId(0代表一级分类,其他值代表子分类)。我做了一个电商通用目录按钮,想实现点击展开菜单选择分类后,把该分类的ParentId填入输入框(比如选“服饰鞋包”后输入框显示其ID8)。

我用useState创建了parentId状态,点击菜单选项时能正常更新这个状态,但提交表单时Formik里的parentId值始终没变化,始终是初始的0。相关代码如下:

import React, {FC, useState} from "react";
import {Box, Button, Stack, SxProps, TextField} from "@mui/material";
import {useFormik} from "formik";

// own modules
import Catalog from "../catalog/Catalog";

const CreateCategory: FC = (sx?: SxProps) => {
    const [parentId, setParentId] = useState<number>(0);

    const formik = useFormik({
        initialValues: {
            name: "",
            label: "",
            parentId: parentId
        },
        onSubmit: (values, {setSubmitting}) => {
            console.log("values: ", values); // value of the parentId doesn't change on submit
            // ...other code...
            setSubmitting(false);
        }
    });

    console.log("parentId: ", parentId); // but value of the parentId in the state change properly
    return (
        <Box
            sx={{
                display: "grid",
                justifyContent: "flex-start",
                gridTemplateColumns: "1fr 1fr 1fr 1fr",
                gap: "2rem",
                "> *": {
                    gridColumn: "span 2"
                },
                ...sx
            }}
            component="form"
            onSubmit={formik.handleSubmit}
        >
            <TextField
                error={!!(formik.errors.name && formik.touched.name)}
                name="name"
                onChange={formik.handleChange}
                value={formik.values.name}
                label="Name (technical - in Latin)"
                variant="outlined"
                helperText={formik.errors.name}
            />
            <TextField
                error={!!(formik.errors.label && formik.touched.label)}
                name="label"
                onChange={formik.handleChange}
                value={formik.values.label}
                label="label(for users)"
                variant="outlined"
                helperText={formik.errors.label}
            />
            <Stack direction="row" spacing={2}>
                <TextField
                    sx={{flexGrow: 1}}
                    error={!!(formik.errors.parentId && formik.touched.parentId)}
                    name="parentId"
                    value={parentId}
                    label="parentId"
                    variant="outlined"
                    helperText={formik.errors.parentId}
                />
                <Catalog onClickOverload={setParentId} /> {/*custom catalog button*/}
                <Button
                    sx={{height: "100%", width: "max-content"}}
                    onClick={() => setParentId(0)} // you can change this value(0 to 10, example) to check, that initial value(0) doesn't change in the formik values
                >
                    Set parent (0)
                </Button>
            </Stack>
            <Button
                sx={{gridColumnStart: 2, gridColumnEnd: 4}}
                type="submit"
                disabled={formik.isSubmitting}
                variant="outlined">
                Create
            </Button>
        </Box>
    )
}

export default CreateCategory;

错误原因与解决方法

核心问题

你把Formik的表单状态和自己的parentId状态分开维护了:

  • Formik的initialValues只会在组件初始化时读取一次parentId的初始值0,之后外部parentId变化不会自动同步到Formik内部的values里
  • 你给ParentId对应的TextField绑定的是自己的parentId状态,但没有把这个值同步回Formik的表单状态,所以Formik完全不知道这个值发生了变化

两种修复方案

方案一:完全用Formik管理状态(推荐)

去掉自己的useState,直接用Formik提供的setFieldValue方法来更新parentId,这样表单状态完全由Formik统一管理,避免状态不一致的问题:

修改后的完整代码:

import React, {FC} from "react";
import {Box, Button, Stack, SxProps, TextField} from "@mui/material";
import {useFormik} from "formik";

// own modules
import Catalog from "../catalog/Catalog";

const CreateCategory: FC = (sx?: SxProps) => {
    const formik = useFormik({
        initialValues: {
            name: "",
            label: "",
            parentId: 0
        },
        onSubmit: (values, {setSubmitting}) => {
            console.log("values: ", values); // 现在parentId会正确更新
            // ...other code...
            setSubmitting(false);
        }
    });

    return (
        <Box
            sx={{
                display: "grid",
                justifyContent: "flex-start",
                gridTemplateColumns: "1fr 1fr 1fr 1fr",
                gap: "2rem",
                "> *": {
                    gridColumn: "span 2"
                },
                ...sx
            }}
            component="form"
            onSubmit={formik.handleSubmit}
        >
            <TextField
                error={!!(formik.errors.name && formik.touched.name)}
                name="name"
                onChange={formik.handleChange}
                value={formik.values.name}
                label="Name (technical - in Latin)"
                variant="outlined"
                helperText={formik.errors.name}
            />
            <TextField
                error={!!(formik.errors.label && formik.touched.label)}
                name="label"
                onChange={formik.handleChange}
                value={formik.values.label}
                label="label(for users)"
                variant="outlined"
                helperText={formik.errors.label}
            />
            <Stack direction="row" spacing={2}>
                <TextField
                    sx={{flexGrow: 1}}
                    error={!!(formik.errors.parentId && formik.touched.parentId)}
                    name="parentId"
                    value={formik.values.parentId}
                    label="parentId"
                    variant="outlined"
                    helperText={formik.errors.parentId}
                    // 加上onChange确保手动输入也能同步Formik状态
                    onChange={formik.handleChange}
                />
                {/* 用Formik的setFieldValue更新parentId */}
                <Catalog onClickOverload={(id) => formik.setFieldValue("parentId", id)} />
                <Button
                    sx={{height: "100%", width: "max-content"}}
                    onClick={() => formik.setFieldValue("parentId", 0)}
                >
                    Set parent (0)
                </Button>
            </Stack>
            <Button
                sx={{gridColumnStart: 2, gridColumnEnd: 4}}
                type="submit"
                disabled={formik.isSubmitting}
                variant="outlined">
                Create
            </Button>
        </Box>
    )
}

export default CreateCategory;

方案二:同步useState和Formik状态(不推荐,多状态维护易出错)

如果一定要保留自己的parentId状态,需要在parentId变化时同步更新Formik的值,同时给TextField加上onChange事件同步回自己的状态:

// 先导入useEffect
import React, {FC, useState, useEffect} from "react";

// 在组件内部添加useEffect同步状态
useEffect(() => {
    formik.setFieldValue("parentId", parentId);
}, [parentId, formik]);

// 然后修改TextField的onChange属性
<TextField
    // ...其他属性不变
    onChange={(e) => {
        const value = Number(e.target.value);
        setParentId(value);
    }}
/>

为什么之前的代码不行?

Formik的initialValues是组件初始化时的快照,后续外部状态变化不会自动触发Formik内部values的更新。你只更新了自己的parentId状态,但Formik的表单状态根本没收到这个变化通知,所以提交时还是会使用初始化时的0值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 16:16:06