diff --git a/.gitignore b/.gitignore index 1799fa8e..38e890c0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ # misc .DS_Store .vscode -.idea/ +.idea # umi .umi diff --git a/docs/demo/initialValues.md b/docs/demo/initialValues.md new file mode 100644 index 00000000..575c4ca9 --- /dev/null +++ b/docs/demo/initialValues.md @@ -0,0 +1,4 @@ +## initialValues + + + diff --git a/docs/examples/initialValues.tsx b/docs/examples/initialValues.tsx new file mode 100644 index 00000000..28ff4372 --- /dev/null +++ b/docs/examples/initialValues.tsx @@ -0,0 +1,67 @@ +/* eslint-disable react/prop-types */ + +import React, { useState } from 'react'; +import Form from 'rc-field-form'; +import Input from './components/Input'; + +const { Field, List } = Form; + +const formValue = { + test: "test", + users: [{ first: "aaa", last: "bbb" }] +}; + +export default () => { + const [form] = Form.useForm(); + const [show, setShow] = useState(false); + + return ( + <> + + {show && ( +
{ + console.log('Submit:', values); + }} + > + + {() => ( + + + + )} + + + {(fields) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + <> + + + + + + + + ))} + + )} + +
+ )} + + ); +}; diff --git a/src/useForm.ts b/src/useForm.ts index 8432a807..a94e7ff7 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -35,6 +35,7 @@ import { setValue, setValues, } from './utils/valueUtil'; +import cloneDeep from './utils/cloneDeep'; type InvalidateFieldEntity = { INVALIDATE_NAME_PATH: InternalNamePath }; @@ -129,11 +130,13 @@ export class FormStore { private setInitialValues = (initialValues: Store, init: boolean) => { this.initialValues = initialValues || {}; if (init) { - this.store = setValues({}, initialValues, this.store); + this.store = setValues({}, this.store, initialValues); } }; - private getInitialValue = (namePath: InternalNamePath) => getValue(this.initialValues, namePath); + private getInitialValue = (namePath: InternalNamePath) => { + return cloneDeep(getValue(this.initialValues, namePath)); + }; private setCallbacks = (callbacks: Callbacks) => { this.callbacks = callbacks; @@ -549,14 +552,13 @@ export class FormStore { // un-register field callback return (isListField?: boolean, preserve?: boolean, subNamePath: InternalNamePath = []) => { this.fieldEntities = this.fieldEntities.filter(item => item !== entity); - // Clean up store value if not preserve const mergedPreserve = preserve !== undefined ? preserve : this.preserve; if (mergedPreserve === false && (!isListField || subNamePath.length > 1)) { const namePath = entity.getNamePath(); - const defaultValue = isListField ? undefined : getValue(this.initialValues, namePath); + const defaultValue = isListField ? undefined : this.getInitialValue(namePath); if ( namePath.length && diff --git a/src/utils/cloneDeep.ts b/src/utils/cloneDeep.ts new file mode 100644 index 00000000..d12467b2 --- /dev/null +++ b/src/utils/cloneDeep.ts @@ -0,0 +1,25 @@ +function cloneDeep(val) { + if (Array.isArray(val)) { + return cloneArrayDeep(val); + } else if (typeof val === 'object' && val !== null) { + return cloneObjectDeep(val); + } + return val; +} + +function cloneObjectDeep(val) { + if (Object.getPrototypeOf(val) === Object.prototype) { + const res = {}; + for (const key in val) { + res[key] = cloneDeep(val[key]); + } + return res; + } + return val; +} + +function cloneArrayDeep(val) { + return val.map(item => cloneDeep(item)); +} + +export default cloneDeep; diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index dd342ad7..25527bd6 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -2,6 +2,7 @@ import get from 'rc-util/lib/utils/get'; import set from 'rc-util/lib/utils/set'; import type { InternalNamePath, NamePath, Store, StoreValue, EventArgs } from '../interface'; import { toArray } from './typeUtil'; +import cloneDeep from '../utils/cloneDeep'; /** * Convert name to internal supported format. @@ -64,7 +65,8 @@ function internalSetValues(store: T, values: T): T { // If both are object (but target is not array), we use recursion to set deep value const recursive = isObject(prevValue) && isObject(value); - newStore[key] = recursive ? internalSetValues(prevValue, value || {}) : value; + + newStore[key] = recursive ? internalSetValues(prevValue, value || {}) : cloneDeep(value); // Clone deep for arrays }); return newStore; diff --git a/tests/index.test.js b/tests/index.test.js index 3168dc39..5870598a 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -726,4 +726,81 @@ describe('Form.Basic', () => { wrapper.find('Input').simulate('change', { event: { target: { value: 'Light' } } }); }).not.toThrowError(); }); + + it('setFieldsValue for List should work', () => { + const Demo = () => { + const [form] = useForm(); + + const handelReset = () => { + form.setFieldsValue({ + users: [], + }); + }; + + const initialValues = { + users: [{ name: '11' }, { name: '22' }], + }; + + return ( +
+ + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + ))} + + )} + + + + +
+ ); + }; + + const wrapper = mount(); + expect(wrapper.find('input').first().getDOMNode().value).toBe('11'); + wrapper.find('.reset-btn').first().simulate('click'); + expect(wrapper.find('input').length).toBe(0); + }); + + it('setFieldsValue should work for multiple Select', () => { + const Select = ({ value, defaultValue }) => { + return
{(value || defaultValue || []).toString()}
; + }; + + const Demo = () => { + const [formInstance] = Form.useForm(); + + React.useEffect(() => { + formInstance.setFieldsValue({ selector: ['K1', 'K2'] }); + }, [formInstance]); + + return ( +
+ + + + )} + + + {fields => ( + <> + {fields.map(({ key, name, ...restField }) => ( + <> + + + + + + + + ))} + + )} + +
+ )} + + ); + }; + + const wrapper = mount(); + wrapper.find('button').simulate('click'); + expect(formValue.users[0].last).toEqual('bbb'); + wrapper.find('button').simulate('click'); + expect(formValue.users[0].last).toEqual('bbb'); + wrapper.find('button').simulate('click'); + expect(wrapper.find('.first-name-input').first().find('input').instance().value).toEqual('aaa'); }); describe('Field with initialValue', () => { @@ -237,21 +278,12 @@ describe('Form.InitialValues', () => { expect(wrapper.find('input').props().value).toEqual('story'); // First reset will get nothing - wrapper - .find('button') - .first() - .simulate('click'); + wrapper.find('button').first().simulate('click'); expect(wrapper.find('input').props().value).toEqual(''); // Change field initialValue and reset - wrapper - .find('button') - .last() - .simulate('click'); - wrapper - .find('button') - .first() - .simulate('click'); + wrapper.find('button').last().simulate('click'); + wrapper.find('button').first().simulate('click'); expect(wrapper.find('input').props().value).toEqual('light'); }); diff --git a/tests/utils.test.js b/tests/utils.test.js index 6d727a8b..7bc47894 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,5 +1,6 @@ import { move, isSimilar, setValues } from '../src/utils/valueUtil'; import NameMap from '../src/utils/NameMap'; +import cloneDeep from '../src/utils/cloneDeep'; describe('utils', () => { describe('arrayMove', () => { @@ -71,4 +72,12 @@ describe('utils', () => { }); }); }); + + describe('clone deep', () => { + it('should not deep clone Class', () => { + const data = { a: new Date() }; + const clonedData = cloneDeep(data); + expect(data.a === clonedData.a).toBeTruthy(); + }); + }); });