diff --git a/.eslintrc.js b/.eslintrc.js
index 799bcd20..b783ff93 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -4,6 +4,7 @@ module.exports = {
...base,
rules: {
...base.rules,
+ 'arrow-parens': 0,
'no-confusing-arrow': 0,
'no-template-curly-in-string': 0,
'prefer-promise-reject-errors': 0,
diff --git a/.prettierrc b/.prettierrc
index f307fb19..60e6c298 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,4 +1,5 @@
{
+ "arrowParens": "avoid",
"endOfLine": "lf",
"semi": true,
"singleQuote": true,
diff --git a/examples/list.tsx b/examples/list.tsx
index 16e5aed9..ef70f70e 100644
--- a/examples/list.tsx
+++ b/examples/list.tsx
@@ -22,6 +22,9 @@ const Demo = () => {
}}
style={{ border: '1px solid red', padding: 15 }}
preserve={false}
+ initialValues={{
+ users: ['little'],
+ }}
>
{() => JSON.stringify(form.getFieldsValue(), null, 2)}
@@ -101,6 +104,15 @@ const Demo = () => {
>
Set List Value
+
+
);
diff --git a/package.json b/package.json
index 0fca592d..45027bdf 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"enzyme-to-json": "^3.1.4",
"father": "^2.13.6",
"np": "^5.0.3",
+ "prettier": "^2.1.2",
"react": "^16.14.0",
"react-dnd": "^8.0.3",
"react-dnd-html5-backend": "^8.0.3",
diff --git a/src/Field.tsx b/src/Field.tsx
index 301737f3..a05c0888 100644
--- a/src/Field.tsx
+++ b/src/Field.tsx
@@ -77,6 +77,9 @@ export interface InternalFieldProps {
/** @private Passed by Form.List props. Do not use since it will break by path check. */
isListField?: boolean;
+ /** @private Passed by Form.List props. Do not use since it will break by path check. */
+ isList?: boolean;
+
/** @private Pass context as prop instead of context api
* since class component can not get context in constructor */
fieldContext: InternalFormInstance;
@@ -361,6 +364,8 @@ class Field extends React.Component implements F
public isListField = () => this.props.isListField;
+ public isList = () => this.props.isList;
+
// ============================= Child Component =============================
public getMeta = (): Meta => {
// Make error & validating in cache to save perf
diff --git a/src/List.tsx b/src/List.tsx
index 26a66d88..30694969 100644
--- a/src/List.tsx
+++ b/src/List.tsx
@@ -54,7 +54,13 @@ const List: React.FunctionComponent = ({ name, children, rules, valid
return (
-
+
{({ value = [], onChange }, meta) => {
const { getFieldValue } = context;
const getNewValue = () => {
diff --git a/src/interface.ts b/src/interface.ts
index e29c2020..433ebe5e 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -98,6 +98,7 @@ export interface FieldEntity {
isFieldDirty: () => boolean;
isFieldValidating: () => boolean;
isListField: () => boolean;
+ isList: () => boolean;
validateRules: (options?: ValidateOptions) => Promise;
getMeta: () => Meta;
getNamePath: () => InternalNamePath;
diff --git a/src/useForm.ts b/src/useForm.ts
index a05d4a40..1f90086d 100644
--- a/src/useForm.ts
+++ b/src/useForm.ts
@@ -211,11 +211,11 @@ export class FormStore {
const namePath =
'INVALIDATE_NAME_PATH' in entity ? entity.INVALIDATE_NAME_PATH : entity.getNamePath();
- // Ignore when it's a list item and not specific the namePath,
- // since parent field is already take in count
- if (!nameList && (entity as FieldEntity).isListField?.()) {
- return;
- }
+ // Ignore when it's a list item and not specific the namePath,
+ // since parent field is already take in count
+ if (!nameList && (entity as FieldEntity).isListField?.()) {
+ return;
+ }
if (!filterFunc) {
filteredNameList.push(namePath);
@@ -287,22 +287,41 @@ export class FormStore {
isAllFieldsTouched = arg1;
}
- const testTouched = (field: FieldEntity) => {
- // Not provide `nameList` will check all the fields
- if (!namePathList) {
- return field.isFieldTouched();
- }
+ const fieldEntities = this.getFieldEntities(true);
+ const isFieldTouched = (field: FieldEntity) => field.isFieldTouched();
+
+ // ===== Will get fully compare when not config namePathList =====
+ if (!namePathList) {
+ return isAllFieldsTouched
+ ? fieldEntities.every(isFieldTouched)
+ : fieldEntities.some(isFieldTouched);
+ }
+
+ // Generate a nest tree for validate
+ const map = new NameMap();
+ namePathList.forEach(shortNamePath => {
+ map.set(shortNamePath, []);
+ });
+ fieldEntities.forEach(field => {
const fieldNamePath = field.getNamePath();
- if (containsNamePath(namePathList, fieldNamePath)) {
- return field.isFieldTouched();
- }
- return isAllFieldsTouched;
- };
+
+ // Find matched entity and put into list
+ namePathList.forEach(shortNamePath => {
+ if (shortNamePath.every((nameUnit, i) => fieldNamePath[i] === nameUnit)) {
+ map.update(shortNamePath, list => [...list, field]);
+ }
+ });
+ });
+
+ // Check if NameMap value is touched
+ const isNamePathListTouched = (entities: FieldEntity[]) => entities.some(isFieldTouched);
+
+ const namePathListEntities = map.map(({ value }) => value);
return isAllFieldsTouched
- ? this.getFieldEntities(true).every(testTouched)
- : this.getFieldEntities(true).some(testTouched);
+ ? namePathListEntities.every(isNamePathListTouched)
+ : namePathListEntities.some(isNamePathListTouched);
};
private isFieldTouched = (name: NamePath) => {
diff --git a/tests/list.test.tsx b/tests/list.test.tsx
index b0ab29b3..7f25e90f 100644
--- a/tests/list.test.tsx
+++ b/tests/list.test.tsx
@@ -4,7 +4,7 @@ import { mount, ReactWrapper } from 'enzyme';
import { resetWarned } from 'rc-util/lib/warning';
import Form, { Field, List, FormProps } from '../src';
import { ListField, ListOperations, ListProps } from '../src/List';
-import { Meta } from '../src/interface';
+import { FormInstance, Meta } from '../src/interface';
import { Input } from './common/InfoField';
import { changeValue, getField } from './common';
import timeout from './common/timeout';
@@ -646,4 +646,97 @@ describe('Form.List', () => {
wrapper.find('button').simulate('click');
expect(onValuesChange).toHaveBeenCalledWith(expect.anything(), { list: [{ first: 'light' }] });
});
+
+ describe('isFieldTouched edge case', () => {
+ it('virtual object', () => {
+ const formRef = React.createRef();
+ const wrapper = mount(
+
+
+
+
+
+
+ ,
+ );
+
+ // Not changed
+ expect(formRef.current.isFieldTouched('user')).toBeFalsy();
+ expect(formRef.current.isFieldsTouched(['user'], false)).toBeFalsy();
+ expect(formRef.current.isFieldsTouched(['user'], true)).toBeFalsy();
+
+ // Changed
+ wrapper
+ .find('input')
+ .first()
+ .simulate('change', { target: { value: '' } });
+
+ expect(formRef.current.isFieldTouched('user')).toBeTruthy();
+ expect(formRef.current.isFieldsTouched(['user'], false)).toBeTruthy();
+ expect(formRef.current.isFieldsTouched(['user'], true)).toBeTruthy();
+ });
+
+ it('List children change', () => {
+ const [wrapper] = generateForm(
+ fields => (
+
+ {fields.map(field => (
+
+
+
+ ))}
+
+ ),
+ {
+ initialValues: { list: ['light', 'bamboo'] },
+ },
+ );
+
+ // Not changed yet
+ expect(form.isFieldTouched('list')).toBeFalsy();
+ expect(form.isFieldsTouched(['list'], false)).toBeFalsy();
+ expect(form.isFieldsTouched(['list'], true)).toBeFalsy();
+
+ // Change children value
+ wrapper
+ .find('input')
+ .first()
+ .simulate('change', { target: { value: 'little' } });
+
+ expect(form.isFieldTouched('list')).toBeTruthy();
+ expect(form.isFieldsTouched(['list'], false)).toBeTruthy();
+ expect(form.isFieldsTouched(['list'], true)).toBeTruthy();
+ });
+
+ it('List self change', () => {
+ const [wrapper] = generateForm((fields, opt) => (
+
+ {fields.map(field => (
+
+
+
+ ))}
+
+ ));
+
+ // Not changed yet
+ expect(form.isFieldTouched('list')).toBeFalsy();
+ expect(form.isFieldsTouched(['list'], false)).toBeFalsy();
+ expect(form.isFieldsTouched(['list'], true)).toBeFalsy();
+
+ // Change children value
+ wrapper.find('button').simulate('click');
+
+ expect(form.isFieldTouched('list')).toBeTruthy();
+ expect(form.isFieldsTouched(['list'], false)).toBeTruthy();
+ expect(form.isFieldsTouched(['list'], true)).toBeTruthy();
+ });
+ });
});