mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-28 21:19:37 +08:00
fix: Form.Item name 0
processing (#21186)
* fix Form.Item name `0` processing * Test coverage for `null`
This commit is contained in:
parent
b4519407b8
commit
3a7235679b
@ -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(
|
||||
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user