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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 16 additions & 8 deletions src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ class Field extends React.Component<InternalFieldProps, FieldState> 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;

Expand Down Expand Up @@ -162,10 +166,10 @@ class Field extends React.Component<InternalFieldProps, FieldState> 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;
};
Expand Down Expand Up @@ -553,11 +557,15 @@ function WrapperField<Values = any>({ name, ...restProps }: FieldProps<Values>)
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 <Field key={key} name={namePath} {...restProps} fieldContext={fieldContext} />;
Expand Down
12 changes: 7 additions & 5 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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);
}
}
};
Expand Down
9 changes: 7 additions & 2 deletions src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
202 changes: 154 additions & 48 deletions tests/preserve.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(
<Demo removeField={false} onFinish={onFinish} formPreserve={false} fieldPreserve />,
Expand All @@ -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
initialValues={{ list: ['light', 'bamboo', 'little'] }}
preserve={false}
ref={instance => {
form = instance;
}}
>
<Form.List name="list">
{(fields, { remove }) => {
return (
<div>
const wrapper = mount(
<Form
initialValues={{ list: ['light', 'bamboo', 'little'] }}
preserve={false}
ref={instance => {
form = instance;
}}
>
<Form.List name="list">
{(fields, { remove }) => {
return (
<div>
{fields.map(field => (
<Form.Field {...field}>
<input />
</Form.Field>
))}
<button
type="button"
onClick={() => {
remove(0);
}}
/>
</div>
);
}}
</Form.List>
</Form>,
);

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
ref={instance => {
form = instance;
}}
initialValues={{ list: ['bamboo'] }}
>
<Form.List name="list">
{(fields, { remove }) => (
<>
{fields.map(field => (
<Form.Field {...field}>
<Form.Field {...field} preserve={false}>
<input />
</Form.Field>
))}
<button
type="button"
onClick={() => {
remove(0);
}}
/>
</div>
);
>
Remove
</button>
</>
)}
</Form.List>
</Form>,
);

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
initialValues={{ list: [{ type: 'light' }] }}
preserve={false}
ref={instance => {
form = instance;
}}
</Form.List>
</Form>,
);
>
<Form.List name="list">
{(fields, { remove }) => {
return (
<>
{fields.map(field => (
<div key={field.key}>
<Form.Field {...field} name={[field.name, 'type']}>
<Input />
</Form.Field>
<Form.Field shouldUpdate>
{(_, __, { getFieldValue }) =>
getFieldValue(['list', field.name, 'type']) === 'light' ? (
<Form.Field
{...field}
key="light"
preserve={false}
name={[field.name, 'light']}
>
<Input />
</Form.Field>
) : (
<Form.Field
{...field}
key="bamboo"
preserve={false}
name={[field.name, 'bamboo']}
>
<Input />
</Form.Field>
)
}
</Form.Field>
</div>
))}
<button
onClick={() => {
remove(0);
}}
>
Remove
</button>
</>
);
}}
</Form.List>
</Form>,
);

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(
<Form initialValues={{ list: ['bamboo'] }}>
<Form.List name="list">
{fields =>
fields.map(field => (
<Form.Field {...field} preserve={false}>
<input />
</Form.Field>
))
}
</Form.List>
</Form>,
);
// 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', () => {
Expand Down