Skip to content

Commit dd8f086

Browse files
authored
feat: Form.List nest Field.Item support preserve={false} (#258)
* chore: Update comment * test: Test driven * fix: preserve can delete nest field * support omit value
1 parent ea532d3 commit dd8f086

File tree

5 files changed

+185
-64
lines changed

5 files changed

+185
-64
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"dependencies": {
5252
"@babel/runtime": "^7.8.4",
5353
"async-validator": "^3.0.3",
54-
"rc-util": "^5.0.0"
54+
"rc-util": "^5.8.0"
5555
},
5656
"devDependencies": {
5757
"@types/enzyme": "^3.10.5",

src/Field.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F
107107
resetCount: 0,
108108
};
109109

110-
private cancelRegisterFunc: (isListField?: boolean, preserve?: boolean) => void | null = null;
110+
private cancelRegisterFunc: (
111+
isListField?: boolean,
112+
preserve?: boolean,
113+
namePath?: InternalNamePath,
114+
) => void | null = null;
111115

112116
private mounted = false;
113117

@@ -162,10 +166,10 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F
162166
}
163167

164168
public cancelRegister = () => {
165-
const { preserve, isListField } = this.props;
169+
const { preserve, isListField, name } = this.props;
166170

167171
if (this.cancelRegisterFunc) {
168-
this.cancelRegisterFunc(isListField, preserve);
172+
this.cancelRegisterFunc(isListField, preserve, getNamePath(name));
169173
}
170174
this.cancelRegisterFunc = null;
171175
};
@@ -553,11 +557,15 @@ function WrapperField<Values = any>({ name, ...restProps }: FieldProps<Values>)
553557
key = `_${(namePath || []).join('_')}`;
554558
}
555559

556-
if (process.env.NODE_ENV !== 'production') {
557-
warning(
558-
restProps.preserve !== false || !restProps.isListField,
559-
'`preserve` should not apply on Form.List fields.',
560-
);
560+
// Warning if it's a directly list field.
561+
// We can still support multiple level field preserve.
562+
if (
563+
process.env.NODE_ENV !== 'production' &&
564+
restProps.preserve === false &&
565+
restProps.isListField &&
566+
namePath.length <= 1
567+
) {
568+
warning(false, '`preserve` should not apply on Form.List fields.');
561569
}
562570

563571
return <Field key={key} name={namePath} {...restProps} fieldContext={fieldContext} />;

src/useForm.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -536,14 +536,16 @@ export class FormStore {
536536
}
537537

538538
// un-register field callback
539-
return (isListField?: boolean, preserve?: boolean) => {
539+
return (isListField?: boolean, preserve?: boolean, subNamePath: InternalNamePath = []) => {
540540
this.fieldEntities = this.fieldEntities.filter(item => item !== entity);
541541

542-
// Clean up store value if preserve
542+
// Clean up store value if not preserve
543543
const mergedPreserve = preserve !== undefined ? preserve : this.preserve;
544-
if (mergedPreserve === false && !isListField) {
544+
545+
if (mergedPreserve === false && (!isListField || subNamePath.length > 1)) {
545546
const namePath = entity.getNamePath();
546-
const defaultValue = getValue(this.initialValues, namePath);
547+
548+
const defaultValue = isListField ? undefined : getValue(this.initialValues, namePath);
547549

548550
if (
549551
namePath.length &&
@@ -554,7 +556,7 @@ export class FormStore {
554556
!matchNamePath(field.getNamePath(), namePath),
555557
)
556558
) {
557-
this.store = setValue(this.store, namePath, defaultValue);
559+
this.store = setValue(this.store, namePath, defaultValue, true);
558560
}
559561
}
560562
};

src/utils/valueUtil.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ export function getValue(store: Store, namePath: InternalNamePath) {
1919
return value;
2020
}
2121

22-
export function setValue(store: Store, namePath: InternalNamePath, value: StoreValue): Store {
23-
const newStore = set(store, namePath, value);
22+
export function setValue(
23+
store: Store,
24+
namePath: InternalNamePath,
25+
value: StoreValue,
26+
removeIfUndefined = false,
27+
): Store {
28+
const newStore = set(store, namePath, value, removeIfUndefined);
2429
return newStore;
2530
}
2631

tests/preserve.test.tsx

Lines changed: 154 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import React from 'react';
33
import { mount } from 'enzyme';
44
import Form, { FormInstance } from '../src';
5-
import InfoField from './common/InfoField';
5+
import InfoField, { Input } from './common/InfoField';
66
import timeout from './common/timeout';
77

88
describe('Form.Preserve', () => {
@@ -99,7 +99,7 @@ describe('Form.Preserve', () => {
9999
expect(onFinish).toHaveBeenCalledWith({ test: 'light' });
100100
});
101101

102-
it('form perishable but field !perishable', async () => {
102+
it('form preserve but field !preserve', async () => {
103103
const onFinish = jest.fn();
104104
const wrapper = mount(
105105
<Demo removeField={false} onFinish={onFinish} formPreserve={false} fieldPreserve />,
@@ -117,67 +117,173 @@ describe('Form.Preserve', () => {
117117
await matchTest(false, { keep: 233, remove: 666 });
118118
});
119119

120-
it('form perishable should not crash Form.List', async () => {
121-
let form: FormInstance;
120+
describe('Form.List', () => {
121+
it('form preserve should not crash', async () => {
122+
let form: FormInstance;
122123

123-
const wrapper = mount(
124-
<Form
125-
initialValues={{ list: ['light', 'bamboo', 'little'] }}
126-
preserve={false}
127-
ref={instance => {
128-
form = instance;
129-
}}
130-
>
131-
<Form.List name="list">
132-
{(fields, { remove }) => {
133-
return (
134-
<div>
124+
const wrapper = mount(
125+
<Form
126+
initialValues={{ list: ['light', 'bamboo', 'little'] }}
127+
preserve={false}
128+
ref={instance => {
129+
form = instance;
130+
}}
131+
>
132+
<Form.List name="list">
133+
{(fields, { remove }) => {
134+
return (
135+
<div>
136+
{fields.map(field => (
137+
<Form.Field {...field}>
138+
<input />
139+
</Form.Field>
140+
))}
141+
<button
142+
type="button"
143+
onClick={() => {
144+
remove(0);
145+
}}
146+
/>
147+
</div>
148+
);
149+
}}
150+
</Form.List>
151+
</Form>,
152+
);
153+
154+
wrapper.find('button').simulate('click');
155+
wrapper.update();
156+
157+
expect(form.getFieldsValue()).toEqual({ list: ['bamboo', 'little'] });
158+
});
159+
160+
it('warning when Form.List use preserve', () => {
161+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
162+
let form: FormInstance;
163+
164+
const wrapper = mount(
165+
<Form
166+
ref={instance => {
167+
form = instance;
168+
}}
169+
initialValues={{ list: ['bamboo'] }}
170+
>
171+
<Form.List name="list">
172+
{(fields, { remove }) => (
173+
<>
135174
{fields.map(field => (
136-
<Form.Field {...field}>
175+
<Form.Field {...field} preserve={false}>
137176
<input />
138177
</Form.Field>
139178
))}
140179
<button
141-
type="button"
142180
onClick={() => {
143181
remove(0);
144182
}}
145-
/>
146-
</div>
147-
);
183+
>
184+
Remove
185+
</button>
186+
</>
187+
)}
188+
</Form.List>
189+
</Form>,
190+
);
191+
192+
expect(errorSpy).toHaveBeenCalledWith(
193+
'Warning: `preserve` should not apply on Form.List fields.',
194+
);
195+
196+
errorSpy.mockRestore();
197+
198+
// Remove should not work
199+
wrapper.find('button').simulate('click');
200+
expect(form.getFieldsValue()).toEqual({ list: [] });
201+
});
202+
203+
it('multiple level field can use preserve', async () => {
204+
let form: FormInstance;
205+
206+
const wrapper = mount(
207+
<Form
208+
initialValues={{ list: [{ type: 'light' }] }}
209+
preserve={false}
210+
ref={instance => {
211+
form = instance;
148212
}}
149-
</Form.List>
150-
</Form>,
151-
);
213+
>
214+
<Form.List name="list">
215+
{(fields, { remove }) => {
216+
return (
217+
<>
218+
{fields.map(field => (
219+
<div key={field.key}>
220+
<Form.Field {...field} name={[field.name, 'type']}>
221+
<Input />
222+
</Form.Field>
223+
<Form.Field shouldUpdate>
224+
{(_, __, { getFieldValue }) =>
225+
getFieldValue(['list', field.name, 'type']) === 'light' ? (
226+
<Form.Field
227+
{...field}
228+
key="light"
229+
preserve={false}
230+
name={[field.name, 'light']}
231+
>
232+
<Input />
233+
</Form.Field>
234+
) : (
235+
<Form.Field
236+
{...field}
237+
key="bamboo"
238+
preserve={false}
239+
name={[field.name, 'bamboo']}
240+
>
241+
<Input />
242+
</Form.Field>
243+
)
244+
}
245+
</Form.Field>
246+
</div>
247+
))}
248+
<button
249+
onClick={() => {
250+
remove(0);
251+
}}
252+
>
253+
Remove
254+
</button>
255+
</>
256+
);
257+
}}
258+
</Form.List>
259+
</Form>,
260+
);
152261

153-
wrapper.find('button').simulate('click');
154-
wrapper.update();
262+
// Change light value
263+
wrapper
264+
.find('input')
265+
.last()
266+
.simulate('change', { target: { value: '1128' } });
155267

156-
expect(form.getFieldsValue()).toEqual({ list: ['bamboo', 'little'] });
157-
});
268+
// Change type
269+
wrapper
270+
.find('input')
271+
.first()
272+
.simulate('change', { target: { value: 'bamboo' } });
158273

159-
it('warning when Form.List use preserve', () => {
160-
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
161-
162-
mount(
163-
<Form initialValues={{ list: ['bamboo'] }}>
164-
<Form.List name="list">
165-
{fields =>
166-
fields.map(field => (
167-
<Form.Field {...field} preserve={false}>
168-
<input />
169-
</Form.Field>
170-
))
171-
}
172-
</Form.List>
173-
</Form>,
174-
);
274+
// Change bamboo value
275+
wrapper
276+
.find('input')
277+
.last()
278+
.simulate('change', { target: { value: '903' } });
175279

176-
expect(errorSpy).toHaveBeenCalledWith(
177-
'Warning: `preserve` should not apply on Form.List fields.',
178-
);
280+
expect(form.getFieldsValue()).toEqual({ list: [{ type: 'bamboo', bamboo: '903' }] });
179281

180-
errorSpy.mockRestore();
282+
// ============== Remove Test ==============
283+
// Remove field
284+
wrapper.find('button').simulate('click');
285+
expect(form.getFieldsValue()).toEqual({ list: [] });
286+
});
181287
});
182288

183289
it('nest render props should not clean full store', () => {

0 commit comments

Comments
 (0)