Skip to content

Commit da3a716

Browse files
committed
fix(FormList): setFields failure in nested components
1 parent 4713100 commit da3a716

File tree

5 files changed

+106
-63
lines changed

5 files changed

+106
-63
lines changed

packages/components/form/FormItem.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
import { flattenDeep, get, isEqual, isFunction, isObject, isString, merge, set, unset } from 'lodash-es';
12
import React, { forwardRef, ReactNode, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
23
import {
34
CheckCircleFilledIcon as TdCheckCircleFilledIcon,
45
CloseCircleFilledIcon as TdCloseCircleFilledIcon,
56
ErrorCircleFilledIcon as TdErrorCircleFilledIcon,
67
} from 'tdesign-icons-react';
7-
import { flattenDeep, get, isEqual, isFunction, isObject, isString, merge, set, unset } from 'lodash-es';
8-
import { StyledProps } from '../common';
98
import useConfig from '../hooks/useConfig';
109
import useDefaultProps from '../hooks/useDefaultProps';
1110
import useGlobalIcon from '../hooks/useGlobalIcon';
@@ -17,34 +16,40 @@ import { parseMessage, validate as validateModal } from './formModel';
1716
import { HOOK_MARK } from './hooks/useForm';
1817
import useFormItemInitialData, { ctrlKeyMap } from './hooks/useFormItemInitialData';
1918
import useFormItemStyle from './hooks/useFormItemStyle';
19+
import { calcFieldValue } from './utils';
20+
21+
import type { StyledProps } from '../common';
2022
import type {
23+
FieldData,
2124
FormInstanceFunctions,
2225
FormItemValidateMessage,
2326
FormRule,
2427
NamePath,
2528
TdFormItemProps,
29+
TdFormProps,
30+
ValidateTriggerType,
2631
ValueType,
2732
} from './type';
28-
import { calcFieldValue } from './utils';
2933

3034
export interface FormItemProps extends TdFormItemProps, StyledProps {
3135
children?: React.ReactNode | React.ReactNode[] | ((form: FormInstanceFunctions) => React.ReactElement);
3236
}
3337

3438
export interface FormItemInstance {
3539
name?: NamePath;
36-
isUpdated?: boolean;
3740
value?: any;
38-
getValue?: Function;
39-
setValue?: Function;
40-
setField?: Function;
41-
validate?: Function;
42-
resetField?: Function;
43-
setValidateMessage?: Function;
44-
getValidateMessage?: Function;
45-
resetValidate?: Function;
46-
validateOnly?: Function;
41+
isUpdated?: boolean;
4742
isFormList?: boolean;
43+
formListMapRef?: React.MutableRefObject<Map<any, any>>;
44+
getValue?: () => any;
45+
setValue?: (newVal: any, originalData?: any) => void;
46+
setField?: (field: Omit<FieldData, 'name'>, originalData?: any) => void;
47+
validate?: (trigger?: ValidateTriggerType, showErrorMessage?: boolean) => Promise<Record<string, any>>;
48+
validateOnly?: (trigger?: ValidateTriggerType) => Promise<Record<string, any>>;
49+
resetField?: (type?: TdFormProps['resetType']) => void;
50+
setValidateMessage?: (message: FormItemValidateMessage[]) => void;
51+
getValidateMessage?: FormInstanceFunctions['getValidateMessage'];
52+
resetValidate?: () => void;
4853
}
4954

5055
const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref) => {
@@ -108,7 +113,6 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
108113
const [formValue, setFormValue] = useState(() => {
109114
const fieldName = flattenDeep([formListName, name]);
110115
const storeValue = get(form?.store, fieldName);
111-
// if (!storeValue && formListName) return; // TODO 针对新增空的动态表单情况,避免回填默认值
112116
return (
113117
storeValue ??
114118
getDefaultInitialData({
@@ -225,7 +229,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
225229
return null;
226230
};
227231

228-
async function analysisValidateResult(trigger) {
232+
async function analysisValidateResult(trigger: ValidateTriggerType) {
229233
const result = {
230234
successList: [],
231235
errorList: [],
@@ -262,7 +266,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
262266
return result;
263267
}
264268

265-
async function validate(trigger = 'all', showErrorMessage?: boolean) {
269+
async function validate(trigger: ValidateTriggerType = 'all', showErrorMessage?: boolean) {
266270
if (innerFormItemsRef.current.length) {
267271
return innerFormItemsRef.current.map((innerFormItem) => innerFormItem?.validate(trigger, showErrorMessage));
268272
}
@@ -316,7 +320,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
316320
};
317321
}
318322

319-
async function validateOnly(trigger = 'all') {
323+
async function validateOnly(trigger: ValidateTriggerType = 'all') {
320324
const { errorList: innerErrorList, resultList } = await analysisValidateResult(trigger);
321325

322326
return {
@@ -331,7 +335,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
331335
filterRules.length && validate('blur');
332336
}
333337

334-
function getResetValue(resetType: string): ValueType {
338+
function getResetValue(resetType: TdFormProps['resetType']): ValueType {
335339
if (resetType === 'initial') {
336340
return getDefaultInitialData({
337341
children,
@@ -351,7 +355,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
351355
return emptyValue;
352356
}
353357

354-
function resetField(type: string) {
358+
function resetField(type: TdFormProps['resetType']) {
355359
if (typeof name === 'undefined') return;
356360

357361
const resetType = type || resetTypeFromContext;
@@ -373,7 +377,7 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
373377
setVerifyStatus(ValidateStatus.VALIDATING);
374378
}
375379

376-
function setField(field: { value?: string; status?: ValidateStatus; validateMessage?: FormItemValidateMessage }) {
380+
function setField(field: Omit<FieldData, 'name'>) {
377381
const { value, status, validateMessage } = field;
378382
if (typeof status !== 'undefined') {
379383
setErrorList(validateMessage ? [validateMessage] : []);

packages/components/form/FormList.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ const FormList: React.FC<TdFormListProps> = (props) => {
182182
(): FormItemInstance => ({
183183
name,
184184
isFormList: true,
185+
formListMapRef,
185186
getValue() {
186187
const formListValue = [];
187188
[...formListMapRef.current.values()].forEach((formItemRef) => {
@@ -214,26 +215,26 @@ const FormList: React.FC<TdFormListProps> = (props) => {
214215
});
215216
},
216217
// TODO 支持局部更新数据
217-
setValue: (fieldData: any[], originData) => {
218+
setValue: (fieldData, originalData) => {
218219
setListFields(
219220
fieldData,
220221
(formItemRef, data) => {
221222
formItemRef?.current?.setValue?.(data);
222223
},
223-
originData,
224+
originalData,
224225
);
225226
},
226-
setField: (fieldData: { value?: any[]; status?: string }, originData) => {
227+
setField: (fieldData, originalData) => {
227228
const { value, status } = fieldData;
228229
setListFields(
229230
value,
230231
(formItemRef, data) => {
231232
formItemRef?.current?.setField?.({ value: data, status });
232233
},
233-
originData,
234+
originalData,
234235
);
235236
},
236-
resetField: (type: string) => {
237+
resetField: (type) => {
237238
const resetType = type || resetTypeFromContext;
238239

239240
if (resetType === 'initial') {

packages/components/form/hooks/useInstance.tsx

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { isEmpty, isFunction, isEqual, merge, get, set } from 'lodash-es';
1+
import { get, isEmpty, isEqual, isFunction, merge, set } from 'lodash-es';
22
import log from '@tdesign/common-js/log/index';
3+
import useConfig from '../../hooks/useConfig';
4+
import { calcFieldValue, findFormItem, findFormItemDeep, objectToArray, travelMapFromObject } from '../utils';
5+
6+
import type { FormItemInstance } from '../FormItem';
37
import type {
4-
TdFormProps,
5-
FormValidateResult,
8+
AllValidateResult,
69
FormResetParams,
710
FormValidateMessage,
8-
AllValidateResult,
11+
FormValidateResult,
912
NamePath,
13+
TdFormProps,
1014
} from '../type';
11-
import useConfig from '../../hooks/useConfig';
12-
import { getMapValue, objectToArray, travelMapFromObject, calcFieldValue } from '../utils';
1315

1416
// 检测是否需要校验 默认全量校验
1517
function needValidate(name: NamePath, fields: string[]) {
@@ -40,7 +42,7 @@ function formatValidateResult(validateResultList) {
4042

4143
export default function useInstance(
4244
props: TdFormProps,
43-
formRef,
45+
formRef: React.RefObject<HTMLFormElement>,
4446
formMapRef: React.MutableRefObject<Map<any, any>>,
4547
floatingFormDataRef: React.RefObject<Record<any, any>>,
4648
) {
@@ -109,7 +111,7 @@ export default function useInstance(
109111
function getFieldValue(name: NamePath) {
110112
if (!name) return null;
111113

112-
const formItemRef = getMapValue(name, formMapRef);
114+
const formItemRef = findFormItem(name, formMapRef);
113115
return formItemRef?.current?.getValue?.();
114116
}
115117

@@ -130,12 +132,12 @@ export default function useInstance(
130132
}
131133
} else {
132134
if (!Array.isArray(nameList)) {
133-
log.error('Form', '`getFieldsValue` 参数需要 Array 类型');
135+
log.error('Form', 'The parameter of "getFieldsValue" must be an array');
134136
return {};
135137
}
136138

137139
nameList.forEach((name) => {
138-
const formItemRef = getMapValue(name, formMapRef);
140+
const formItemRef = findFormItem(name, formMapRef);
139141
if (!formItemRef) return;
140142

141143
const fieldValue = calcFieldValue(name, formItemRef?.current.getValue?.());
@@ -175,13 +177,15 @@ export default function useInstance(
175177

176178
// 对外方法,设置对应 formItem 的数据
177179
function setFields(fields = []) {
178-
if (!Array.isArray(fields)) throw new Error('setFields 参数需要 Array 类型');
180+
if (!Array.isArray(fields)) throw new TypeError('The parameter of "setFields" must be an array');
179181

180182
fields.forEach((field) => {
181183
const { name, ...restFields } = field;
182-
const formItemRef = getMapValue(name, formMapRef);
183-
184-
formItemRef?.current?.setField(restFields, field);
184+
let formItemRef = findFormItem(name, formMapRef);
185+
if (!formItemRef) {
186+
formItemRef = findFormItemDeep(name, formMapRef);
187+
}
188+
formItemRef?.current?.setField(restFields);
185189
});
186190
}
187191

@@ -196,7 +200,7 @@ export default function useInstance(
196200
const { type = 'initial', fields = [] } = params;
197201

198202
fields.forEach((name) => {
199-
const formItemRef = getMapValue(name, formMapRef);
203+
const formItemRef = findFormItem(name, formMapRef);
200204
formItemRef?.current?.resetField(type);
201205
});
202206
}
@@ -211,10 +215,10 @@ export default function useInstance(
211215
formItemRef?.current?.resetValidate();
212216
});
213217
} else {
214-
if (!Array.isArray(fields)) throw new Error('clearValidate 参数需要 Array 类型');
218+
if (!Array.isArray(fields)) throw new TypeError('The parameter of "clearValidate" must be an array');
215219

216220
fields.forEach((name) => {
217-
const formItemRef = getMapValue(name, formMapRef);
221+
const formItemRef = findFormItem(name, formMapRef);
218222
formItemRef?.current?.resetValidate();
219223
});
220224
}
@@ -229,25 +233,30 @@ export default function useInstance(
229233

230234
// 对外方法,获取 formItem 的错误信息
231235
function getValidateMessage(fields?: Array<keyof FormData>) {
232-
const message = {};
236+
const formItemRefs =
237+
typeof fields === 'undefined'
238+
? [...formMapRef.current.values()]
239+
: fields.map((name) => findFormItem(name, formMapRef)).filter(Boolean);
233240

234-
if (typeof fields === 'undefined') {
235-
[...formMapRef.current.values()].forEach((formItemRef) => {
236-
const item = formItemRef?.current?.getValidateMessage?.();
237-
if (isEmpty(item)) return;
238-
message[formItemRef?.current?.name] = item;
239-
});
240-
} else {
241-
if (!Array.isArray(fields)) throw new Error('getValidateMessage 参数需要 Array 类型');
242-
243-
fields.forEach((name) => {
244-
const formItemRef = getMapValue(name, formMapRef);
245-
const item = formItemRef?.current?.getValidateMessage?.();
246-
if (isEmpty(item)) return;
247-
message[formItemRef?.current?.name] = item;
248-
});
241+
if (typeof fields !== 'undefined' && !Array.isArray(fields)) {
242+
throw new TypeError('The parameter of "getValidateMessage" must be an array');
249243
}
250244

245+
const extractValidateMessage = (formItemRef: React.RefObject<FormItemInstance>) => {
246+
const item = formItemRef?.current?.getValidateMessage?.();
247+
if (isEmpty(item)) return null;
248+
const nameKey = Array.isArray(formItemRef?.current?.name)
249+
? formItemRef?.current?.name.join('.')
250+
: String(formItemRef?.current?.name);
251+
return { nameKey, item };
252+
};
253+
254+
const message = {};
255+
formItemRefs.forEach((formItemRef) => {
256+
const result = extractValidateMessage(formItemRef);
257+
if (result) message[result.nameKey] = result.item;
258+
});
259+
251260
if (isEmpty(message)) return;
252261

253262
return message;

packages/components/form/type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ export interface FormResetParams<FormData> {
447447

448448
export interface FieldData {
449449
name: NamePath;
450-
value?: unknown;
450+
value?: any;
451451
status?: string;
452452
validateMessage?: { type?: string; message?: string };
453453
}

packages/components/form/utils/index.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { has, get, isObject, isArray, isEmpty } from 'lodash-es';
1+
import { get, has, isArray, isEmpty, isObject } from 'lodash-es';
2+
import type { FormItemInstance } from '../FormItem';
23
import type { NamePath } from '../type';
34

4-
// 获取 formMap 管理的数据
5-
export function getMapValue(name: NamePath, formMapRef: React.MutableRefObject<Map<any, any>>) {
5+
/**
6+
* 在 `formMap` 中查找指定的 `FormItem`
7+
* (仅查找当前层级)
8+
*/
9+
export function findFormItem(
10+
name: NamePath,
11+
formMapRef: React.MutableRefObject<Map<any, any>>,
12+
): React.RefObject<FormItemInstance> | undefined {
613
if (!formMapRef.current) return;
7-
814
// 提取所有 map key
915
const mapKeys = [...formMapRef.current.keys()];
1016
// 转译为字符串后比对 key 兼容数组格式
@@ -13,6 +19,29 @@ export function getMapValue(name: NamePath, formMapRef: React.MutableRefObject<M
1319
return formMapRef.current.get(key);
1420
}
1521

22+
/**
23+
* 在 `formMap` 中查找指定的 `FormItem`
24+
* (包括在嵌套的 FormList 中递归查找)
25+
*/
26+
export function findFormItemDeep(
27+
name: NamePath,
28+
formMapRef: React.MutableRefObject<Map<any, any>>,
29+
): React.RefObject<FormItemInstance> | undefined {
30+
if (!formMapRef?.current) return;
31+
const targetPath = Array.isArray(name) ? name : [name];
32+
for (const [key, ref] of formMapRef.current.entries()) {
33+
const formItem = ref?.current;
34+
if (!formItem?.isFormList || !formItem.formListMapRef) continue;
35+
const { formListMapRef } = formItem;
36+
for (const [itemKey, itemRef] of formListMapRef.current.entries()) {
37+
const fullPath = [key, itemKey].flat();
38+
if (String(fullPath) === String(targetPath)) return itemRef;
39+
}
40+
const found = findFormItemDeep(name, formListMapRef);
41+
if (found) return found;
42+
}
43+
}
44+
1645
// { user: { name: '' } } => [['user', 'name']]
1746
// 不处理数组类型
1847
// { user: [{ name: '' }]} => [['user']]

0 commit comments

Comments
 (0)