fix: Form.Item name 0 processing (#21186)

* fix Form.Item name `0` processing

* Test coverage for `null`
This commit is contained in:
Belikov Ivan 2020-02-03 18:54:33 +09:00 committed by GitHub
parent b4519407b8
commit 3a7235679b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 103 additions and 7 deletions

View File

@ -3,7 +3,7 @@ import isEqual from 'lodash/isEqual';
import classNames from 'classnames';
import { Field, FormInstance } from 'rc-field-form';
import { FieldProps } from 'rc-field-form/lib/Field';
import { Meta } from 'rc-field-form/lib/interface';
import {Meta, NamePath} from 'rc-field-form/lib/interface';
import omit from 'omit.js';
import Row from '../grid/row';
import { ConfigContext } from '../config-provider';
@ -40,6 +40,17 @@ export interface FormItemProps
fieldKey?: number;
}
function hasValidName(name?: NamePath): Boolean {
if (name === null) {
warning(
false,
'Form.Item',
'`null` is passed as `name` property',
);
}
return !(name === undefined || name === null);
}
function FormItem(props: FormItemProps): React.ReactElement {
const {
name,
@ -67,6 +78,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
const [inlineErrors, setInlineErrors] = React.useState<Record<string, string[]>>({});
const { name: formName } = formContext;
const hasName = hasValidName(name);
// Cache Field NamePath
const nameRef = React.useRef<(string | number)[]>([]);
@ -189,7 +201,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
const isRenderProps = typeof children === 'function';
if (name === undefined && !isRenderProps && !dependencies) {
if (!hasName && !isRenderProps && !dependencies) {
return renderLayout(children as ChildrenNodeType);
}
@ -240,21 +252,21 @@ function FormItem(props: FormItemProps): React.ReactElement {
};
let childNode: ChildrenNodeType = null;
if (Array.isArray(children) && !!name) {
if (Array.isArray(children) && hasName) {
warning(false, 'Form.Item', '`children` is array of render props cannot have `name`.');
childNode = children;
} else if (isRenderProps && (!shouldUpdate || !!name)) {
} else if (isRenderProps && (!shouldUpdate || hasName)) {
warning(
!!shouldUpdate,
'Form.Item',
'`children` of render props only work with `shouldUpdate`.',
);
warning(
!name,
!hasName,
'Form.Item',
"Do not use `name` with `children` of render props since it's not a field.",
);
} else if (dependencies && !isRenderProps && !name) {
} else if (dependencies && !isRenderProps && !hasName) {
warning(
false,
'Form.Item',
@ -279,7 +291,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
});
childNode = React.cloneElement(children, childProps);
} else if (isRenderProps && shouldUpdate && !name) {
} else if (isRenderProps && shouldUpdate && !hasName) {
childNode = (children as RenderChildren)(context);
} else {
warning(

View File

@ -112,6 +112,77 @@ describe('Form', () => {
</Form.Item>
</Form.Item>
));
it('correct onFinish values', async () => {
async function click(wrapper, className) {
wrapper
.find(className)
.last()
.simulate('click');
await delay();
wrapper.update();
}
const onFinish = jest.fn().mockImplementation(() => {});
const wrapper = mount(
<Form
onFinish={(v) => {
if (typeof v.list[0] === 'object') {
/* old version led to SyntheticEvent be passed as an value here
that led to weird infinite loop somewhere and OutOfMemory crash */
v = new Error('We expect value to be a primitive here');
}
onFinish(v);
}}
>
<Form.List name="list">
{(fields, { add, remove }) => (
<>
{fields.map(field => (
// key is in a field
// eslint-disable-next-line react/jsx-key
<Form.Item {...field}>
<Input />
</Form.Item>
))}
<Button
className="add"
onClick={add}
>
Add
</Button>
<Button
className="remove"
onClick={() => remove(0)}
>
Remove
</Button>
</>
)}
</Form.List>
</Form>,
);
await click(wrapper, '.add');
await change(wrapper, 0, 'input1');
wrapper.find('form').simulate('submit');
await delay();
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1'] });
await click(wrapper, '.add');
await change(wrapper, 1, 'input2');
await click(wrapper, '.add');
await change(wrapper, 2, 'input3');
wrapper.find('form').simulate('submit');
await delay();
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1', 'input2', 'input3'] });
await click(wrapper, '.remove'); // will remove first input
wrapper.find('form').simulate('submit');
await delay();
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input2', 'input3'] });
});
});
it('noStyle Form.Item', async () => {
@ -438,4 +509,17 @@ describe('Form', () => {
expect(wrapper.find('Field')).toHaveLength(1);
});
it('`null` triggers warning and is treated as `undefined`', () => {
const wrapper = mount(
<Form.Item name={null}>
<input />
</Form.Item>,
);
expect(wrapper.find('Field')).toHaveLength(0);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Form.Item] `null` is passed as `name` property',
);
});
});