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

React Hook Form重渲染后无法显示默认值问题

问题

react-hook-form无法加载从localStorage中获取的默认值。这些默认值通过useEffect读取localStorage后更新unuploadedShoeData状态,但组件重渲染后表单并未同步这些值。

已知情况:

  • 若默认值在表单初始渲染前就存在(比如硬编码值),表单能正常显示默认值
  • 日志确认unuploadedShoeData已被正确设置,但表单未触发重渲染
原因分析

核心问题在于:

  1. fields数组在组件每次渲染时重新创建,但自定义的Form组件(react-hook-form封装)仅在初始化阶段读取initial值,后续fields变化不会触发表单更新默认值
  2. 当unuploadedShoeData从null更新为实际数据时,shoe变量会变化,但Form组件未感知到该变化,也未重新初始化表单值
解决方案

提供两种可行方案,可根据实际场景选择:

方案一:给Form组件添加key触发重初始化

当shoe变化时,通过修改key属性让Form组件重新挂载,从而读取新的initial值:

<Form
  key={shoe ? JSON.stringify(shoe) : 'empty'}
  fields={fields}
  onSubmit={edit ? editShoe : uploadShoe}
  submitButtonText={edit ? "Update" : "Sell"}
/>

方案二:在Form内部监听fields变化并调用reset

若不想让Form组件重新挂载,可在自定义Form组件中添加逻辑,监听fields变化并调用react-hook-form的reset方法更新表单值:

// 自定义Form组件内部示例
import { useForm, useEffect } from 'react-hook-form';

const Form = ({ fields, onSubmit, submitButtonText }) => {
  const { register, handleSubmit, reset } = useForm();

  useEffect(() => {
    // 从fields中提取最新初始值
    const initialValues = fields.reduce((acc, field) => {
      acc[field.name] = field.initial;
      return acc;
    }, {});
    reset(initialValues);
  }, [fields, reset]);

  // 表单渲染逻辑...
};

额外优化建议

  • 用useMemo缓存shoe计算结果,避免每次渲染重复执行过滤操作:
const shoe = useMemo(() => {
  return edit
    ? shoes.filter((shoe) => shoe.id === parseInt(id)).pop()
    : unuploadedShoeData ?? null;
}, [edit, shoes, id, unuploadedShoeData]);
  • 用useMemo缓存fields数组,依赖shoe和shoeOptions,避免不必要的数组重新创建:
const fields = useMemo(() => [
  // 原fields数组内容
], [shoe, shoeOptions]);
修改后的完整代码
import { useMemo, useEffect, useState } from 'react';
// 其他必要导入...

const ShoeUpload = ({
  edit = false,
  user,
  setShoes,
  shoeOptions,
  api,
  shoes,
}) => {
  const classes = useStyles();
  const navigate = useNavigate();
  const { id } = useParams();
  const [loading, setLoading] = useState(false);
  const [unuploadedShoeData, setUnuploadedShoeData] = useState(null);

  const UNUPLOADED_SHOE_DATA_KEY = "unuploadedShoeData";

  // 缓存shoe计算结果
  const shoe = useMemo(() => {
    return edit
      ? shoes.filter((shoe) => shoe.id === parseInt(id)).pop()
      : unuploadedShoeData ?? null;
  }, [edit, shoes, id, unuploadedShoeData]);

  console.log("shoe", shoe);

  useEffect(() => {
    const shoeData = localStorage.getItem(UNUPLOADED_SHOE_DATA_KEY);
    if (shoeData) {
      setUnuploadedShoeData(JSON.parse(shoeData));
    }
  }, []);
 
  const _storeUnuploadedShoeData = (shoeData) => {
    const toStore = {
      brand: shoeData.brand.value,
      condition: shoeData.condition,
      foot: shoeData.foot.value,
      gender: shoeData.gender.value,
      icon: shoeData.icon,
      images: shoeData.images,
      price: shoeData.price,
      size: shoeData.size.value,
    };
    localStorage.setItem(UNUPLOADED_SHOE_DATA_KEY, JSON.stringify(toStore));
  };

  const uploadShoe = (data) => {     
    if (unuploadedShoeData) {
      localStorage.removeItem(UNUPLOADED_SHOE_DATA_KEY);
    }

    setLoading(true);
    api
      .createShoe(
        user.id,
        data.brand.value,
        data.icon,
        data.foot.value,
        data.gender.value,
        data.size.value,
        data.condition,
        data.price,
        data.images
      )
      .then((shoe) => {
        setLoading(false);
        setShoes((prevShoes) => [...prevShoes, shoe]);
        Analytics.uploadShoe(shoe);
        navigate(`/shoes/${shoe.id}`);
      });
  };

  const editShoe = (data) => {
    setLoading(true);
    api
      .editShoe(
        shoe.id,
        data.brand.value,
        data.icon,
        data.foot.value,
        data.gender.value,
        data.size.value,
        data.condition,
        data.price,
        data.images,
        shoe.images
      )
      .then((shoe) => {
        setShoes((prevShoes) =>
          prevShoes.map((prevShoe) =>
            prevShoe.id === shoe.id ? shoe : prevShoe
          )
        );
        setLoading(false);
        navigate(`/shoes/${shoe.id}`);
      });
  };

  // 缓存fields数组
  const fields = useMemo(() => [
    {
      name: "brand",
      label: "Shoe Brand",
      required: true,
      component: "autocomplete",
      options: shoeOptions?.brand,
      initial: shoe
        ? shoeOptions?.brand
            ?.filter((brand) => brand.value === shoe.brand)
            .pop()
        : null,
    },
    {
      name: "icon",
      label: "Shoe Icon",
      required: true,
      initial: shoe ? shoe.icon : null,
    },
    {
      name: "foot",
      label: "Foot",
      required: true,
      component: "autocomplete",
      options: shoeOptions?.foot,
      initial: shoe
        ? shoeOptions?.foot?.filter((foot) => foot.value === shoe.foot).pop()
        : null,
    },
    {
      name: "gender",
      label: "Gender",
      required: true,
      component: "autocomplete",
      options: shoeOptions?.gender,
      initial: shoe
        ? shoeOptions?.gender
            ?.filter((gender) => gender.value === shoe.gender)
            .pop()
        : null,
    },
    {
      name: "size",
      label: "Size (UK)",
      required: true,
      component: "autocomplete",
      options: shoeOptions?.size,
      initial: shoe
        ? shoeOptions?.size?.filter((size) => size.value === shoe.size).pop()
        : null,
    },
    {
      name: "price",
      label: "Price (£)",
      required: true,
      type: "number",
      initial: shoe ? shoe.price : null,
    },
    {
      name: "condition",
      label: "Condition",
      required: true,
      multiline: true,
      initial: shoe ? shoe.condition : null,
    },
    {
      name: "images",
      label: "Images",
      required: true,
      component: "imageUpload",
      acceptedFiles: ["image/jpeg", "image/png"],
      filesLimit: 6,
      initial: shoe ? shoe.images?.map((image) => image.image) : null,
    },
  ], [shoe, shoeOptions]);

  return (
    <div>
      {loading ? (
        <Grid
          container
          justifyContent="center"
          alignItems="center"
          direction="column"
          minHeight="80vh"
        >
          <Grid item xs={12} my={2}>
            <Typography variant="body1">Just a second...</Typography>
          </Grid>
          <Grid item xs={12}>
            <CircularProgress />
          </Grid>
        </Grid>
      ) : (
        <Box container className={classes.container}>
          <Card variant="outlined" className={classes.card}>
            <CardContent>
              <Typography variant="h6" component="h1" textAlign="center" mb={2}>
                {edit ? "Edit shoe" : "Upload a shoe to sell"}
              </Typography>
              <Form
                key={shoe ? JSON.stringify(shoe) : 'empty'}
                fields={fields}
                onSubmit={edit ? editShoe : uploadShoe}
                submitButtonText={edit ? "Update" : "Sell"}
              />
            </CardContent>
          </Card>
        </Box>
      )}
    </div>
  );
};

export default ShoeUpload;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:52:01