Skip to content

Conversation

@RylanBot
Copy link
Collaborator

@RylanBot RylanBot commented Nov 10, 2025

🤔 这个 PR 的性质是?

  • 日常 bug 修复
  • 新特性提交
  • 文档改进
  • 演示代码改进
  • 组件样式/交互改进
  • CI/CD 改进
  • 重构
  • 代码风格优化
  • 测试用例
  • 分支合并
  • 其他

🔗 相关 Issue

(非动态表单 ➕ 数字 name 场景)

💡 需求背景和解决方案

测试代码存档
import React from 'react';
import { Button, Form, Input, Space } from 'tdesign-react';

const { FormItem, FormList } = Form;

export default function BaseForm() {
  const [form] = Form.useForm();

  function updateSingleTaskStatus() {
    form.setFieldsValue({
      users: {
        1: {
          projects: {
            0: {
              tasks: {
                0: { status: 'done' },
              },
            },
          },
        },
      },
    });
  }

  function setFields() {
    form.setFields([
      {
        name: 'users',
        value: [
          {
            name: 'Alice',
            projects: [
              {
                projectName: 'Website Redesign',
                tasks: [{ taskName: 'Design mockups', status: 'completed' }],
              },
            ],
          },
        ],
      },
    ]);
  }

  function setFieldsValue() {
    form.setFieldsValue({
      users: [
        {
          name: 'Bob',
          projects: [
            {
              projectName: 'Mobile App',
              tasks: [
                { taskName: 'API integration', status: 'pending' },
                { taskName: 'UI implementation', status: 'in-progress' },
              ],
            },
          ],
        },
      ],
    });
  }

  function addNestedData() {
    form.setFieldsValue({
      users: [
        {
          name: 'Charlie',
          projects: [
            {
              projectName: 'Backend Service',
              tasks: [
                { taskName: 'Database design', status: 'completed' },
                { taskName: 'API development', status: 'in-progress' },
              ],
            },
            {
              projectName: 'DevOps',
              tasks: [{ taskName: 'CI/CD setup', status: 'completed' }],
            },
          ],
        },
        {
          name: 'David',
          projects: [
            {
              projectName: 'Documentation',
              tasks: [{ taskName: 'Write API docs', status: 'in-progress' }],
            },
          ],
        },
      ],
    });
  }

  function validate() {
    form.validate();
  }

  function clearValidate() {
    form.clearValidate();
  }

  // useEffect(() => {
  //   form.setFieldsValue({
  //     users: [
  //       {
  //         name: 'Default User',
  //         projects: [
  //           {
  //             projectName: 'Default Project',
  //             tasks: [{ taskName: 'Default Task', status: 'pending' }],
  //           },
  //         ],
  //       },
  //     ],
  //   });
  // }, []);

  return (
    <Space direction="vertical" align="center">
      <Space>
        <Button onClick={() => form.reset()}>resetToInitial</Button>
        <Button onClick={setFields}>setFields</Button>
        <Button onClick={setFieldsValue}>setFieldsValue</Button>
        <Button onClick={addNestedData}>addNestedData</Button>
        <Button onClick={validate}>validate</Button>
        <Button onClick={clearValidate}>clearValidate</Button>
        <Button onClick={updateSingleTaskStatus}>updateSingleTaskStatus</Button>
      </Space>
      <Form
        initialData={{
          users: [
            {
              name: 'Initial User',
              projects: [
                {
                  projectName: 'Initial Project',
                  tasks: [{ taskName: 'Initial Task', status: 'pending' }],
                },
              ],
            },
          ],
        }}
        resetType="initial"
        style={{ border: '1px solid blue', padding: 10 }}
        form={form}
        onValuesChange={(change) => {
          console.log('>>>>', change, JSON.stringify(change));
        }}
      >
        <FormList name="users">
          {(userFields, { add: addUser, remove: removeUser, move: moveUser }) => (
            <>
              <FormItem>
                <Button id="test-add-user" theme="default" onClick={() => addUser({ name: 'New User', projects: [] })}>
                  新增用户
                </Button>
              </FormItem>
              {userFields.map(({ key: userKey, name: userName, ...userRestField }, userIndex) => (
                <FormItem key={userKey} style={{ border: '1px dotted gray' }}>
                  <Space direction="vertical">
                    <FormItem
                      {...userRestField}
                      name={[userName, 'name']}
                      label="用户名"
                      rules={[{ required: true, type: 'error' }]}
                    >
                      <Input placeholder={`user-name-${userIndex}`} />
                    </FormItem>

                    <FormItem>
                      <Space style={{ display: 'flex' }}>
                        {userFields.length > 1 && userIndex > 0 && (
                          <Button
                            theme="primary"
                            variant="outline"
                            size="small"
                            onClick={() => moveUser(userIndex, userIndex - 1)}
                          >
                            上移用户
                          </Button>
                        )}
                        <Button size="small" theme="warning" onClick={() => removeUser(userName)}>
                          删除用户
                        </Button>
                      </Space>
                    </FormItem>
                  </Space>
                  <Space>
                    <FormList name={[userName, 'projects']}>
                      {(projectFields, { add: addProject, remove: removeProject }) => (
                        <Space direction="vertical">
                          <Button
                            size="small"
                            theme="success"
                            onClick={() => addProject({ projectName: 'New Project', tasks: [] })}
                          >
                            新增项目
                          </Button>
                          <Space>
                            {projectFields.map(
                              ({ key: projectKey, name: projectName, ...projectRestField }, projectIndex) => (
                                <div
                                  key={`${userKey}-${projectKey}`}
                                  style={{
                                    display: 'flex',
                                    flexDirection: 'column',
                                    border: '1px dotted red',
                                  }}
                                >
                                  <FormItem
                                    {...projectRestField}
                                    name={[projectName, 'projectName']}
                                    label="项目名称"
                                    rules={[{ required: true, type: 'error' }]}
                                    style={{ backgroundColor: 'lightyellow' }}
                                  >
                                    <Input placeholder={`project-name-${userIndex}-${projectIndex}`} />
                                  </FormItem>

                                  <Button size="small" theme="danger" onClick={() => removeProject(projectName)}>
                                    删除项目
                                  </Button>

                                  <FormList name={[projectName, 'tasks']}>
                                    {(taskFields, { add: addTask, remove: removeTask }) => (
                                      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                                        <Button
                                          size="small"
                                          theme="success"
                                          variant="outline"
                                          onClick={() => addTask()}
                                        >
                                          新增任务
                                        </Button>
                                        {taskFields.map(
                                          ({ key: taskKey, name: taskName, ...taskRestField }, taskIndex) => (
                                            <div
                                              key={`${userKey}-${projectKey}-${taskKey}`}
                                              style={{
                                                display: 'flex',
                                                flexDirection: 'column',
                                                gap: 6,
                                                border: '1px solid pink',
                                                padding: 8,
                                              }}
                                            >
                                              <FormItem
                                                {...taskRestField}
                                                name={[taskName, 'taskName']}
                                                label="任务名称"
                                                rules={[{ required: true, type: 'error' }]}
                                                style={{ backgroundColor: 'lightcyan' }}
                                              >
                                                <Input
                                                  placeholder={`task-name-${userIndex}-${projectIndex}-${taskIndex}`}
                                                />
                                              </FormItem>
                                              <FormItem
                                                {...taskRestField}
                                                name={[taskName, 'status']}
                                                label="状态"
                                                rules={[{ required: true, type: 'error' }]}
                                              >
                                                <Input
                                                  placeholder={`task-status-${userIndex}-${projectIndex}-${taskIndex}`}
                                                />
                                              </FormItem>

                                              <Button size="small" theme="warning" onClick={() => removeTask(taskName)}>
                                                删除任务
                                              </Button>
                                            </div>
                                          ),
                                        )}
                                      </div>
                                    )}
                                  </FormList>
                                </div>
                              ),
                            )}
                          </Space>
                        </Space>
                      )}
                    </FormList>
                  </Space>
                </FormItem>
              ))}
            </>
          )}
        </FormList>
      </Form>
    </Space>
  );
}
  • 新增 fullPath 的计算,确保复杂的路径能匹配正确

📝 更新日志

  • fix(Form): 修复嵌套三层及以上的 FormList 相关方法失效的问题

  • fix(Form): 优化 key 的生成,更新值与当前表单值相同时不刷新元素

  • fix(Form): 修复 reset 时没有触发 onValueChange 的问题

  • fix(Form): 修复初始化调用 setFieldsValue 时没有触发 onValuesChange 的问题

  • fix(Form): 修复非动态表单场景下,name 为数字或含有数字时 setFieldValues 失败的问题

  • 本条 PR 不需要纳入 Changelog

☑️ 请求合并前的自查清单

⚠️ 请自检并全部勾选全部选项⚠️

  • 文档已补充或无须补充
  • 代码演示已提供或无须提供
  • TypeScript 定义已补充或无须补充
  • Changelog 已提供或无须提供

@RylanBot RylanBot requested a review from honkinglin as a code owner November 10, 2025 08:03
@RylanBot RylanBot added the WIP work in porgess label Nov 10, 2025
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch from 8d008d5 to 0f8dd66 Compare November 10, 2025 08:03
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 10, 2025

tdesign-react-demo

npm i https://pkg.pr.new/tdesign-react@3957

commit: 288e1c3

@github-actions
Copy link
Contributor

github-actions bot commented Nov 10, 2025

完成

@RylanBot RylanBot removed the WIP work in porgess label Nov 10, 2025
@RylanBot RylanBot closed this Nov 10, 2025
@RylanBot RylanBot reopened this Nov 10, 2025
@RylanBot RylanBot requested a review from Copilot November 11, 2025 06:53
Copilot finished reviewing on behalf of RylanBot November 11, 2025 06:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes critical issues with nested FormLists (3+ levels deep) by introducing fullPath tracking for FormItem and FormList components. The changes ensure proper path resolution in complex nested scenarios and prevent data mutation issues through strategic use of cloneDeep.

  • Adds fullPath computation to track complete paths in nested FormList scenarios
  • Implements defensive cloning to prevent accidental data mutation during add/remove operations
  • Fixes multiple form methods (setFieldsValue, reset, validate, clearValidate) to work with deeply nested fields
  • Optimizes key generation to avoid unnecessary re-renders when data hasn't changed

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/components/form/utils/index.ts Adds utility functions normalizeNamePath and concatNamePath for path handling; refactors findFormItemDeep and calcFieldValue to support fullPath
packages/components/form/type.ts Updates onValuesChange type signature from Record<string, unknown> to Record<string, any> for flexibility
packages/components/form/hooks/useInstance.tsx Enhances form instance methods to support deep field lookup using findFormItemDeep; adds cloneDeep for data integrity; implements reset callback with onValuesChange
packages/components/form/hooks/useFormItemInitialData.ts Adds optional chaining for safer array length check
packages/components/form/__tests__/form-list.test.tsx Adds comprehensive test coverage for 3-level nested FormLists with operations like add, remove, validate
packages/components/form/FormList.tsx Core changes: computes fullPath for nested lists; refactors add/remove/move operations with cloneDeep; optimizes key generation to reduce re-renders
packages/components/form/FormItem.tsx Integrates fullPath calculation; updates all map operations and value updates to use fullPath
packages/components/form/FormContext.tsx Adds fullPath field to FormListContext for parent path tracking
packages/components/form/Form.tsx Adds cloneDeep to onFormItemValueChange callback to prevent external mutation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch 2 times, most recently from aade667 to 2579707 Compare November 11, 2025 14:09
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch from 2579707 to b9bac37 Compare November 11, 2025 19:02
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch from b9bac37 to 628f85b Compare November 11, 2025 19:04
@RylanBot RylanBot closed this Nov 11, 2025
@RylanBot RylanBot reopened this Nov 11, 2025
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch 2 times, most recently from b70cc1f to dfebbfa Compare November 12, 2025 00:54
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch from dfebbfa to b5d16be Compare November 12, 2025 00:56
@RylanBot RylanBot force-pushed the rylan/fix/form-list/nest branch from f7a36d2 to 288e1c3 Compare November 20, 2025 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants