Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# misc
.DS_Store
.vscode
.idea/
.idea

# umi
.umi
Expand Down
4 changes: 4 additions & 0 deletions docs/demo/initialValues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## initialValues


<code src="../examples/initialValues.tsx" />
67 changes: 67 additions & 0 deletions docs/examples/initialValues.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);

return (
<>
<button onClick={() => setShow((prev) => !prev)}>switch show</button>
{show && (
<Form
form={form}
initialValues={formValue}
preserve={false}
onFinish={values => {
console.log('Submit:', values);
}}
>
<Field shouldUpdate>
{() => (
<Field name="test" preserve={false}>
<Input/>
</Field>
)}
</Field>
<List name="users">
{(fields) => (
<>
{fields.map(({ key, name, ...restField }) => (
<>
<Field
{...restField}
name={[name, "first"]}
rules={[
{ required: true, message: "Missing first name" }
]}
>
<Input placeholder="First Name" />
</Field>
<Field
{...restField}
name={[name, "last"]}
rules={[{ required: true, message: "Missing last name" }]}
>
<Input placeholder="Last Name" />
</Field>
</>
))}
</>
)}
</List>
</Form>
)}
</>
);
};
10 changes: 6 additions & 4 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
setValue,
setValues,
} from './utils/valueUtil';
import cloneDeep from './utils/cloneDeep';

type InvalidateFieldEntity = { INVALIDATE_NAME_PATH: InternalNamePath };

Expand Down Expand Up @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修改了一下顺序,防止 initialValues 被覆盖。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是其实 this.store 是没用的?初始化的时候 this.store 应该是空的。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

理论上是的,但我不太确定。
这个方法确实是组件第一次渲染时 init 才会传 true

}
};

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;
Expand Down Expand Up @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里使用有深拷贝的方法获取 defaultValue,因为 this.initialValues 里存的是浅拷贝,直接浅拷贝过来会被赋值行为修改原值。


if (
namePath.length &&
Expand Down
25 changes: 25 additions & 0 deletions src/utils/cloneDeep.ts
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 3 additions & 1 deletion src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -64,7 +65,8 @@ function internalSetValues<T>(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
Copy link
Member Author

@MadCcc MadCcc Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的深拷贝是针对 array 的。之前的做法是把 array 看作 object 处理,但是实际上处理逻辑并不一样,数组应该被直接替换,而不是比对每一个元素进行赋值

});

return newStore;
Expand Down
77 changes: 77 additions & 0 deletions tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Form
form={form}
initialValues={initialValues}
name="dynamic_form_nest_item"
autoComplete="off"
>
<Form.List name="users">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Field
key={key}
{...restField}
name={[name, 'name']}
rules={[{ required: true, message: 'Missing name' }]}
>
<Input placeholder="Name" />
</Field>
))}
</>
)}
</Form.List>
<Field>
<button className="reset-btn" onClick={handelReset}>
reset
</button>
</Field>
</Form>
);
};

const wrapper = mount(<Demo />);
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 <div className="select-div">{(value || defaultValue || []).toString()}</div>;
};

const Demo = () => {
const [formInstance] = Form.useForm();

React.useEffect(() => {
formInstance.setFieldsValue({ selector: ['K1', 'K2'] });
}, [formInstance]);

return (
<Form form={formInstance}>
<Field initialValue="K1" name="selector">
<Select />
</Field>
</Form>
);
};

const wrapper = mount(<Demo />);
expect(wrapper.find('.select-div').text()).toBe('K1,K2');
});
});
Loading