diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index 04844d1b14..9859f6ba53 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -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>({}); 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( diff --git a/components/form/__tests__/index.test.js b/components/form/__tests__/index.test.js index a5d84ade1b..eaa87cdc15 100644 --- a/components/form/__tests__/index.test.js +++ b/components/form/__tests__/index.test.js @@ -112,6 +112,77 @@ describe('Form', () => { )); + + 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( +
{ + 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); + }} + > + + {(fields, { add, remove }) => ( + <> + {fields.map(field => ( + // key is in a field + // eslint-disable-next-line react/jsx-key + + + + ))} + + + + )} + +
, + ); + + 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( + + + , + ); + + expect(wrapper.find('Field')).toHaveLength(0); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [antd: Form.Item] `null` is passed as `name` property', + ); + }); });