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

React Hook Form自动追加动态表单重置时重复添加输入框问题

问题原因

调用reset方法时,useWatch返回的数组会触发多次状态更新:

  1. 第一次是reset将表单值设置为[{text: "X"}],此时最后一项非空,触发append添加空项;
  2. 由于react-hook-form内部状态同步机制,useWatch的监听会再次触发(useWatch与useFieldArray的fields存在状态不同步的可能),导致append被重复调用,最终末尾出现两个空输入框。

另外,useWatch是独立于useFieldArray的监听机制,相比直接使用useFieldArray返回的fields,更容易出现状态不同步的问题。


解决方案

方案1:改用useFieldArray的fields监听(推荐)

直接使用useFieldArray返回的fields替代useWatch,保证状态与useFieldArray完全同步,避免重复触发:

import { useEffect } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";

interface FormValues {
  items: {
    text: string;
  }[];
}

export default function DynamicForm() {
  const { control, reset } = useForm<FormValues>({
    defaultValues: {
      items: [{ text: "" }],
    },
  });

  const { fields, append } = useFieldArray({
    control,
    name: "items",
  });

  // 直接监听fields而非useWatch返回的数组
  useEffect(() => {
    if (fields.length === 0) return;
    const lastItem = fields[fields.length - 1];
    if (lastItem.text.trim() !== "") {
      append({ text: "" }, { shouldFocus: false });
    }
  }, [fields, append]);

  const handleInsertData = () => {
    reset({
      items: [{ text: "X" }],
    });
  };

  return (
    <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md">
      <button onClick={handleInsertData}>Insert Data</button>
      <h2 className="text-xl font-bold mb-4">Dynamic Form</h2>

      <form>
        <div className="space-y-4">
          {fields.map((field, index) => (
            <div key={field.id} className="flex items-start space-x-2">
              <div className="flex-grow">
                <Controller
                  control={control}
                  name={`items.${index}.text`}
                  render={({ field }) => (
                    <textarea
                      {...field}
                      className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
                      placeholder="Enter text..."
                    />
                  )}
                />
              </div>
            </div>
          ))}
        </div>
      </form>
    </div>
  );
}

方案2:添加防重复触发标记

使用ref跟踪是否正在执行append操作,避免同一周期内重复触发:

import { useEffect, useRef } from "react";
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form";

interface FormValues {
  items: {
    text: string;
  }[];
}

export default function DynamicForm() {
  const { control, reset } = useForm<FormValues>({
    defaultValues: {
      items: [{ text: "" }],
    },
  });

  const { fields, append } = useFieldArray({
    control,
    name: "items",
  });

  const watchItems = useWatch({
    control,
    name: "items",
  });
  
  // 用ref标记是否正在追加,避免重复触发
  const isAppendingRef = useRef(false);

  useEffect(() => {
    if (watchItems.length === 0 || isAppendingRef.current) return;
    
    const lastItem = watchItems[watchItems.length - 1];
    if (lastItem.text.trim() !== "") {
      isAppendingRef.current = true;
      append({ text: "" }, { shouldFocus: false });
      // 在下一周期重置标记
      setTimeout(() => {
        isAppendingRef.current = false;
      }, 0);
    }
  }, [watchItems, append]);

  const handleInsertData = () => {
    reset({
      items: [{ text: "X" }],
    });
  };

  return (
    <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md">
      <button onClick={handleInsertData}>Insert Data</button>
      <h2 className="text-xl font-bold mb-4">Dynamic Form</h2>

      <form>
        <div className="space-y-4">
          {fields.map((field, index) => (
            <div key={field.id} className="flex items-start space-x-2">
              <div className="flex-grow">
                <Controller
                  control={control}
                  name={`items.${index}.text`}
                  render={({ field }) => (
                    <textarea
                      {...field}
                      className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
                      placeholder="Enter text..."
                    />
                  )}
                />
              </div>
            </div>
          ))}
        </div>
      </form>
    </div>
  );
}

方案3:重置时暂时禁用自动追加逻辑

通过状态标记重置过程,重置完成后再恢复自动追加:

import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form";

interface FormValues {
  items: {
    text: string;
  }[];
}

export default function DynamicForm() {
  const { control, reset } = useForm<FormValues>({
    defaultValues: {
      items: [{ text: "" }],
    },
  });

  const { fields, append } = useFieldArray({
    control,
    name: "items",
  });

  const watchItems = useWatch({
    control,
    name: "items",
  });
  
  const [isResetting, setIsResetting] = useState(false);

  useEffect(() => {
    if (isResetting || watchItems.length === 0) return;
    
    const lastItem = watchItems[watchItems.length - 1];
    if (lastItem.text.trim() !== "") {
      append({ text: "" }, { shouldFocus: false });
    }
  }, [watchItems, append, isResetting]);

  const handleInsertData = () => {
    setIsResetting(true);
    reset({
      items: [{ text: "X" }],
    });
    // 重置完成后恢复自动追加
    setTimeout(() => {
      setIsResetting(false);
    }, 0);
  };

  return (
    <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md">
      <button onClick={handleInsertData}>Insert Data</button>
      <h2 className="text-xl font-bold mb-4">Dynamic Form</h2>

      <form>
        <div className="space-y-4">
          {fields.map((field, index) => (
            <div key={field.id} className="flex items-start space-x-2">
              <div className="flex-grow">
                <Controller
                  control={control}
                  name={`items.${index}.text`}
                  render={({ field }) => (
                    <textarea
                      {...field}
                      className="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
                      placeholder="Enter text..."
                    />
                  )}
                />
              </div>
            </div>
          ))}
        </div>
      </form>
    </div>
  );
}

最佳实践
  1. 使用useFieldArray时,优先用其返回的fields监听数组状态,避免useWatch带来的状态不同步问题;
  2. 自动追加逻辑需添加防重复触发判断,避免因多次状态更新导致重复操作;
  3. 执行reset等批量修改操作时,可暂时禁用自动逻辑,避免状态冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:50:54