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

React嵌套动态表单开发:动态新增字段状态共享问题求解

问题根源

现有实现将所有YoutubeURL对应的起止时间存储在同一个全局状态数组中,所有URL项渲染时共用同一份时间配置,因此新增URL时会直接复用已有时间状态,修改任意一个URL下的时间配置也会同步到其他所有URL上。

修复思路
  • 调整状态结构,将起止时间列表作为子属性挂载到对应YoutubeURL的配置对象中,每个URL独立维护自己的时间对,从根源上避免状态共享。
  • 适配增删改处理逻辑,操作时间配置时需要同时定位到对应URL的索引、以及该URL下时间对的索引,精准操作目标数据。
  • 删除冗余的全局时间状态,所有表单数据统一存储在URL数组中,结构更贴合业务逻辑。
优化后完整代码
import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";

// 提前定义类型减少后续类型错误
interface TimeItem {
  start: number;
  end: number;
}

interface YoutubeItem {
  youtubeUrl: string;
  playTimes: TimeItem[];
}

const PlaylistForm = () => {
  const [formPlaylistName, setFormPlaylistName] = useState<string>("");
  // 调整状态结构,每个URL项自带独立的时间列表
  const [formPlaylistYoutubeUrl, setFormPlaylistYoutubeUrl] = useState<YoutubeItem[]>([
    { youtubeUrl: "", playTimes: [{ start: 0, end: 0 }] },
  ]);

  // 处理URL输入变更
  const handleChangePlaylistYoutubeUrl = (i: number, e: React.ChangeEvent<HTMLInputElement>) => {
    const newFormValues = [...formPlaylistYoutubeUrl];
    newFormValues[i][e.target.name as keyof YoutubeItem] = e.target.value as never;
    setFormPlaylistYoutubeUrl(newFormValues);
  };

  // 新增URL项
  const addFormFieldsPlaylistYoutubeUrl = () => {
    setFormPlaylistYoutubeUrl([
      ...formPlaylistYoutubeUrl,
      { youtubeUrl: "", playTimes: [{ start: 0, end: 0 }] },
    ]);
  };

  // 删除指定URL项
  const removeFormFieldsPlaylistYoutubeUrl = (i: number) => {
    const newFormValues = [...formPlaylistYoutubeUrl];
    newFormValues.splice(i, 1);
    setFormPlaylistYoutubeUrl(newFormValues);
  };

  // 处理时间配置变更
  const handleChangePlaylistYoutubeUrlPlayTimes = (
    urlIndex: number,
    timeIndex: number,
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    const newFormValues = [...formPlaylistYoutubeUrl];
    newFormValues[urlIndex].playTimes[timeIndex][e.target.name as keyof TimeItem] = Number(e.target.value);
    setFormPlaylistYoutubeUrl(newFormValues);
  };

  // 给指定URL新增时间对
  const addFormFieldsPlaylistYoutubeUrlPlayTimes = (urlIndex: number) => {
    const newFormValues = [...formPlaylistYoutubeUrl];
    newFormValues[urlIndex].playTimes = [
      ...newFormValues[urlIndex].playTimes,
      { start: 0, end: 0 },
    ];
    setFormPlaylistYoutubeUrl(newFormValues);
  };

  // 删除指定URL下的指定时间对
  const removeFormFieldsPlaylistYoutubeUrlPlayTimes = (urlIndex: number, timeIndex: number) => {
    const newFormValues = [...formPlaylistYoutubeUrl];
    newFormValues[urlIndex].playTimes.splice(timeIndex, 1);
    setFormPlaylistYoutubeUrl(newFormValues);
  };

  const handleSubmit = (event: React.FormEvent) => {
    event.preventDefault();
    alert(formPlaylistName);
    alert(JSON.stringify(formPlaylistYoutubeUrl));
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>Playlist name</label>
      <Form.Control
        type="text"
        name="playlistName"
        value={formPlaylistName}
        onChange={(e) => setFormPlaylistName(e.target.value)}
      />
      {formPlaylistYoutubeUrl.map((element, urlIndex) => (
        <div className="form-inline" key={urlIndex}>
          <label>Youtube URL</label>
          <Form.Control
            type="text"
            name="youtubeUrl"
            value={element.youtubeUrl || ""}
            onChange={(e) => handleChangePlaylistYoutubeUrl(urlIndex, e)}
          />

          {urlIndex > 0 && (
            <button
              type="button"
              className="button remove"
              onClick={() => removeFormFieldsPlaylistYoutubeUrl(urlIndex)}
            >
              Remove
            </button>
          )}

          {/* 遍历当前URL独立的时间列表 */}
          {element.playTimes.map((timeItem, timeIndex) => (
            <div className="form-inline" key={timeIndex}>
              <label>Start time</label>
              <Form.Control
                type="text"
                name="start"
                value={timeItem.start || ""}
                onChange={(e) =>
                  handleChangePlaylistYoutubeUrlPlayTimes(urlIndex, timeIndex, e)
                }
              />
              <label>End time</label>
              <Form.Control
                type="text"
                name="end"
                value={timeItem.end || ""}
                onChange={(e) =>
                  handleChangePlaylistYoutubeUrlPlayTimes(urlIndex, timeIndex, e)
                }
              />
              {timeIndex > 0 && (
                <div>
                  <button
                    type="button"
                    className="button remove"
                    onClick={() =>
                      removeFormFieldsPlaylistYoutubeUrlPlayTimes(urlIndex, timeIndex)
                    }
                  >
                    Remove
                  </button>
                </div>
              )}
              <button
                type="button"
                className="button add"
                onClick={() => addFormFieldsPlaylistYoutubeUrlPlayTimes(urlIndex)}
              >
                Add time
              </button>
            </div>
          ))}
        </div>
      ))}

      <div className="button-section">
        <Button
          variant="primary"
          type="button"
          onClick={() => addFormFieldsPlaylistYoutubeUrl()}
        >
          Add new Field
        </Button>
        <Button variant="primary" type="submit">
          Create new shorturl
        </Button>
      </div>
    </form>
  );
};

export default PlaylistForm;
额外优化建议
  • 替换数组索引为唯一ID作为渲染key,避免删除中间项时出现渲染异常。
  • 增加表单校验逻辑,比如校验YoutubeURL格式合法性、起止时间大小合理性,提升表单可用性。
  • 可拆分出YoutubeUrlItem、TimePickerItem子组件,简化主组件逻辑,提升代码可维护性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 08:45:01