diff --git a/package.json b/package.json index 5864777b..37bc8425 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "dependencies": { "@babel/runtime": "^7.8.4", "async-validator": "^3.0.3", - "rc-util": "^5.0.0" + "rc-util": "^5.8.0" }, "devDependencies": { "@types/enzyme": "^3.10.5", diff --git a/src/Field.tsx b/src/Field.tsx index 1f8580f6..c2d211c8 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -107,7 +107,11 @@ class Field extends React.Component implements F resetCount: 0, }; - private cancelRegisterFunc: (isListField?: boolean, preserve?: boolean) => void | null = null; + private cancelRegisterFunc: ( + isListField?: boolean, + preserve?: boolean, + namePath?: InternalNamePath, + ) => void | null = null; private mounted = false; @@ -162,10 +166,10 @@ class Field extends React.Component implements F } public cancelRegister = () => { - const { preserve, isListField } = this.props; + const { preserve, isListField, name } = this.props; if (this.cancelRegisterFunc) { - this.cancelRegisterFunc(isListField, preserve); + this.cancelRegisterFunc(isListField, preserve, getNamePath(name)); } this.cancelRegisterFunc = null; }; @@ -553,11 +557,15 @@ function WrapperField({ name, ...restProps }: FieldProps) key = `_${(namePath || []).join('_')}`; } - if (process.env.NODE_ENV !== 'production') { - warning( - restProps.preserve !== false || !restProps.isListField, - '`preserve` should not apply on Form.List fields.', - ); + // Warning if it's a directly list field. + // We can still support multiple level field preserve. + if ( + process.env.NODE_ENV !== 'production' && + restProps.preserve === false && + restProps.isListField && + namePath.length <= 1 + ) { + warning(false, '`preserve` should not apply on Form.List fields.'); } return ; diff --git a/src/useForm.ts b/src/useForm.ts index ca5e8f8a..86e959fe 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -536,14 +536,16 @@ export class FormStore { } // un-register field callback - return (isListField?: boolean, preserve?: boolean) => { + return (isListField?: boolean, preserve?: boolean, subNamePath: InternalNamePath = []) => { this.fieldEntities = this.fieldEntities.filter(item => item !== entity); - // Clean up store value if preserve + // Clean up store value if not preserve const mergedPreserve = preserve !== undefined ? preserve : this.preserve; - if (mergedPreserve === false && !isListField) { + + if (mergedPreserve === false && (!isListField || subNamePath.length > 1)) { const namePath = entity.getNamePath(); - const defaultValue = getValue(this.initialValues, namePath); + + const defaultValue = isListField ? undefined : getValue(this.initialValues, namePath); if ( namePath.length && @@ -554,7 +556,7 @@ export class FormStore { !matchNamePath(field.getNamePath(), namePath), ) ) { - this.store = setValue(this.store, namePath, defaultValue); + this.store = setValue(this.store, namePath, defaultValue, true); } } }; diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index 851699eb..7f4d0bfa 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -19,8 +19,13 @@ export function getValue(store: Store, namePath: InternalNamePath) { return value; } -export function setValue(store: Store, namePath: InternalNamePath, value: StoreValue): Store { - const newStore = set(store, namePath, value); +export function setValue( + store: Store, + namePath: InternalNamePath, + value: StoreValue, + removeIfUndefined = false, +): Store { + const newStore = set(store, namePath, value, removeIfUndefined); return newStore; } diff --git a/tests/preserve.test.tsx b/tests/preserve.test.tsx index dd1e4861..c496a2f5 100644 --- a/tests/preserve.test.tsx +++ b/tests/preserve.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { mount } from 'enzyme'; import Form, { FormInstance } from '../src'; -import InfoField from './common/InfoField'; +import InfoField, { Input } from './common/InfoField'; import timeout from './common/timeout'; describe('Form.Preserve', () => { @@ -99,7 +99,7 @@ describe('Form.Preserve', () => { expect(onFinish).toHaveBeenCalledWith({ test: 'light' }); }); - it('form perishable but field !perishable', async () => { + it('form preserve but field !preserve', async () => { const onFinish = jest.fn(); const wrapper = mount( , @@ -117,67 +117,173 @@ describe('Form.Preserve', () => { await matchTest(false, { keep: 233, remove: 666 }); }); - it('form perishable should not crash Form.List', async () => { - let form: FormInstance; + describe('Form.List', () => { + it('form preserve should not crash', async () => { + let form: FormInstance; - const wrapper = mount( -
{ - form = instance; - }} - > - - {(fields, { remove }) => { - return ( -
+ const wrapper = mount( + { + form = instance; + }} + > + + {(fields, { remove }) => { + return ( +
+ {fields.map(field => ( + + + + ))} +
+ ); + }} +
+ , + ); + + wrapper.find('button').simulate('click'); + wrapper.update(); + + expect(form.getFieldsValue()).toEqual({ list: ['bamboo', 'little'] }); + }); + + it('warning when Form.List use preserve', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + let form: FormInstance; + + const wrapper = mount( +
{ + form = instance; + }} + initialValues={{ list: ['bamboo'] }} + > + + {(fields, { remove }) => ( + <> {fields.map(field => ( - + ))}
- ); + > + Remove + + + )} +
+ , + ); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: `preserve` should not apply on Form.List fields.', + ); + + errorSpy.mockRestore(); + + // Remove should not work + wrapper.find('button').simulate('click'); + expect(form.getFieldsValue()).toEqual({ list: [] }); + }); + + it('multiple level field can use preserve', async () => { + let form: FormInstance; + + const wrapper = mount( +
{ + form = instance; }} - -
, - ); + > + + {(fields, { remove }) => { + return ( + <> + {fields.map(field => ( +
+ + + + + {(_, __, { getFieldValue }) => + getFieldValue(['list', field.name, 'type']) === 'light' ? ( + + + + ) : ( + + + + ) + } + +
+ ))} + + + ); + }} +
+ , + ); - wrapper.find('button').simulate('click'); - wrapper.update(); + // Change light value + wrapper + .find('input') + .last() + .simulate('change', { target: { value: '1128' } }); - expect(form.getFieldsValue()).toEqual({ list: ['bamboo', 'little'] }); - }); + // Change type + wrapper + .find('input') + .first() + .simulate('change', { target: { value: 'bamboo' } }); - it('warning when Form.List use preserve', () => { - const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - mount( -
- - {fields => - fields.map(field => ( - - - - )) - } - -
, - ); + // Change bamboo value + wrapper + .find('input') + .last() + .simulate('change', { target: { value: '903' } }); - expect(errorSpy).toHaveBeenCalledWith( - 'Warning: `preserve` should not apply on Form.List fields.', - ); + expect(form.getFieldsValue()).toEqual({ list: [{ type: 'bamboo', bamboo: '903' }] }); - errorSpy.mockRestore(); + // ============== Remove Test ============== + // Remove field + wrapper.find('button').simulate('click'); + expect(form.getFieldsValue()).toEqual({ list: [] }); + }); }); it('nest render props should not clean full store', () => {