Skip to content

Commit 09b570a

Browse files
authored
feat: Field support initialValue (#111)
* add test case * logic it * update set logic * Fix refresh logic * Not conlict test * reset test * more test case * more test case * update doc
1 parent 5b97316 commit 09b570a

File tree

10 files changed

+419
-126
lines changed

10 files changed

+419
-126
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ We use typescript to create the Type definition. You can view directly in IDE. B
8181
| dependencies | Will re-render if dependencies changed | [NamePath](#namepath)[] | - |
8282
| getValueFromEvent | Specify how to get value from event | (..args: any[]) => any | - |
8383
| getValueProps | Customize additional props with value. This prop will disable `valuePropName` | (value) => any | - |
84+
| initialValue | Field initial value | any | - |
8485
| name | Field name path | [NamePath](#namepath) | - |
8586
| normalize | Normalize value before update | (value, prevValue, prevValues) => any | - |
8687
| rules | Validate rules | [Rule](#rule)[] | - |

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@
4242
"react": "*"
4343
},
4444
"devDependencies": {
45+
"@types/jest": "^25.2.1",
4546
"@types/lodash": "^4.14.135",
4647
"@types/react": "^16.8.19",
4748
"@types/react-dom": "^16.8.4",
48-
"@types/warning": "^3.0.0",
4949
"enzyme": "^3.1.0",
5050
"enzyme-adapter-react-16": "^1.0.2",
5151
"enzyme-to-json": "^3.1.4",
@@ -63,7 +63,6 @@
6363
"dependencies": {
6464
"@babel/runtime": "^7.8.4",
6565
"async-validator": "^3.0.3",
66-
"rc-util": "^4.17.0",
67-
"warning": "^4.0.3"
66+
"rc-util": "^4.20.3"
6867
}
6968
}

src/Field.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface InternalFieldProps {
7070
valuePropName?: string;
7171
getValueProps?: (value: StoreValue) => object;
7272
messageVariables?: Record<string, string>;
73+
initialValue?: any;
7374
onReset?: () => void;
7475
}
7576

src/FieldContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import warning from 'warning';
2+
import warning from 'rc-util/lib/warning';
33
import { InternalFormInstance } from './interface';
44

55
export const HOOK_MARK = 'RC_FORM_INTERNAL_HOOKS';

src/List.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import warning from 'warning';
2+
import warning from 'rc-util/lib/warning';
33
import { InternalNamePath, NamePath, StoreValue } from './interface';
44
import FieldContext from './FieldContext';
55
import Field from './Field';

src/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export interface FieldEntity {
9494
name?: NamePath;
9595
rules?: Rule[];
9696
dependencies?: NamePath[];
97+
initialValue?: any;
9798
};
9899
}
99100

src/useForm.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import warning from 'warning';
2+
import warning from 'rc-util/lib/warning';
33
import {
44
Callbacks,
55
FieldData,
@@ -308,12 +308,100 @@ export class FormStore {
308308
return this.isFieldsValidating([name]);
309309
};
310310

311+
/**
312+
* Reset Field with field `initialValue` prop.
313+
* Can pass `entities` or `namePathList` or just nothing.
314+
*/
315+
private resetWithFieldInitialValue = (
316+
info: {
317+
entities?: FieldEntity[];
318+
namePathList?: InternalNamePath[];
319+
/** Skip reset if store exist value. This is only used for field register reset */
320+
skipExist?: boolean;
321+
} = {},
322+
) => {
323+
// Create cache
324+
const cache: NameMap<Set<{ entity: FieldEntity; value: any }>> = new NameMap();
325+
326+
const fieldEntities = this.getFieldEntities(true);
327+
fieldEntities.forEach(field => {
328+
const { initialValue } = field.props;
329+
const namePath = field.getNamePath();
330+
331+
// Record only if has `initialValue`
332+
if (initialValue !== undefined) {
333+
const records = cache.get(namePath) || new Set();
334+
records.add({ entity: field, value: initialValue });
335+
336+
cache.set(namePath, records);
337+
}
338+
});
339+
340+
// Reset
341+
const resetWithFields = (entities: FieldEntity[]) => {
342+
entities.forEach(field => {
343+
const { initialValue } = field.props;
344+
345+
if (initialValue !== undefined) {
346+
const namePath = field.getNamePath();
347+
const formInitialValue = this.getInitialValue(namePath);
348+
349+
if (formInitialValue !== undefined) {
350+
// Warning if conflict with form initialValues and do not modify value
351+
warning(
352+
false,
353+
`Form already set 'initialValues' with path '${namePath.join(
354+
'.',
355+
)}'. Field can not overwrite it.`,
356+
);
357+
} else {
358+
const records = cache.get(namePath);
359+
if (records && records.size > 1) {
360+
// Warning if multiple field set `initialValue`and do not modify value
361+
warning(
362+
false,
363+
`Multiple Field with path '${namePath.join(
364+
'.',
365+
)}' set 'initialValue'. Can not decide which one to pick.`,
366+
);
367+
} else if (records) {
368+
const originValue = this.getFieldValue(namePath);
369+
// Set `initialValue`
370+
if (!info.skipExist || originValue === undefined) {
371+
this.store = setValue(this.store, namePath, [...records][0].value);
372+
}
373+
}
374+
}
375+
}
376+
});
377+
};
378+
379+
let requiredFieldEntities: FieldEntity[];
380+
if (info.entities) {
381+
requiredFieldEntities = info.entities;
382+
} else if (info.namePathList) {
383+
requiredFieldEntities = [];
384+
385+
info.namePathList.forEach(namePath => {
386+
const records = cache.get(namePath);
387+
if (records) {
388+
requiredFieldEntities.push(...[...records].map(r => r.entity));
389+
}
390+
});
391+
} else {
392+
requiredFieldEntities = fieldEntities;
393+
}
394+
395+
resetWithFields(requiredFieldEntities);
396+
};
397+
311398
private resetFields = (nameList?: NamePath[]) => {
312399
this.warningUnhooked();
313400

314401
const prevStore = this.store;
315402
if (!nameList) {
316403
this.store = setValues({}, this.initialValues);
404+
this.resetWithFieldInitialValue();
317405
this.notifyObservers(prevStore, null, { type: 'reset' });
318406
return;
319407
}
@@ -324,6 +412,7 @@ export class FormStore {
324412
const initialValue = this.getInitialValue(namePath);
325413
this.store = setValue(this.store, namePath, initialValue);
326414
});
415+
this.resetWithFieldInitialValue({ namePathList });
327416
this.notifyObservers(prevStore, namePathList, { type: 'reset' });
328417
};
329418

@@ -371,6 +460,17 @@ export class FormStore {
371460
private registerField = (entity: FieldEntity) => {
372461
this.fieldEntities.push(entity);
373462

463+
// Set initial values
464+
if (entity.props.initialValue !== undefined) {
465+
const prevStore = this.store;
466+
this.resetWithFieldInitialValue({ entities: [entity], skipExist: true });
467+
this.notifyObservers(prevStore, [entity.getNamePath()], {
468+
type: 'valueUpdate',
469+
source: 'internal',
470+
});
471+
}
472+
473+
// un-register field callback
374474
return () => {
375475
this.fieldEntities = this.fieldEntities.filter(item => item !== entity);
376476
};

src/utils/validateUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import RawAsyncValidator from 'async-validator';
22
import * as React from 'react';
3-
import warning from 'warning';
3+
import warning from 'rc-util/lib/warning';
44
import {
55
InternalNamePath,
66
ValidateOptions,

tests/index.test.js

Lines changed: 0 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -180,125 +180,6 @@ describe('Form.Basic', () => {
180180
});
181181
});
182182

183-
describe('initialValues', () => {
184-
it('works', () => {
185-
let form;
186-
187-
const wrapper = mount(
188-
<div>
189-
<Form
190-
ref={instance => {
191-
form = instance;
192-
}}
193-
initialValues={{ username: 'Light', path1: { path2: 'Bamboo' } }}
194-
>
195-
<Field name="username">
196-
<Input />
197-
</Field>
198-
<Field name={['path1', 'path2']}>
199-
<Input />
200-
</Field>
201-
</Form>
202-
</div>,
203-
);
204-
205-
expect(form.getFieldsValue()).toEqual({
206-
username: 'Light',
207-
path1: {
208-
path2: 'Bamboo',
209-
},
210-
});
211-
expect(form.getFieldsValue(['username'])).toEqual({
212-
username: 'Light',
213-
});
214-
expect(form.getFieldsValue(['path1'])).toEqual({
215-
path1: {
216-
path2: 'Bamboo',
217-
},
218-
});
219-
expect(form.getFieldsValue(['username', ['path1', 'path2']])).toEqual({
220-
username: 'Light',
221-
path1: {
222-
path2: 'Bamboo',
223-
},
224-
});
225-
expect(
226-
getField(wrapper, 'username')
227-
.find('input')
228-
.props().value,
229-
).toEqual('Light');
230-
expect(
231-
getField(wrapper, ['path1', 'path2'])
232-
.find('input')
233-
.props().value,
234-
).toEqual('Bamboo');
235-
});
236-
237-
it('update and reset should use new initialValues', () => {
238-
let form;
239-
let mountCount = 0;
240-
241-
const TestInput = props => {
242-
React.useEffect(() => {
243-
mountCount += 1;
244-
}, []);
245-
246-
return <Input {...props} />;
247-
};
248-
249-
const Test = ({ initialValues }) => (
250-
<Form
251-
ref={instance => {
252-
form = instance;
253-
}}
254-
initialValues={initialValues}
255-
>
256-
<Field name="username">
257-
<Input />
258-
</Field>
259-
<Field name="email">
260-
<TestInput />
261-
</Field>
262-
</Form>
263-
);
264-
265-
const wrapper = mount(<Test initialValues={{ username: 'Bamboo' }} />);
266-
expect(form.getFieldsValue()).toEqual({
267-
username: 'Bamboo',
268-
});
269-
expect(
270-
getField(wrapper, 'username')
271-
.find('input')
272-
.props().value,
273-
).toEqual('Bamboo');
274-
275-
// Should not change it
276-
wrapper.setProps({ initialValues: { username: 'Light' } });
277-
wrapper.update();
278-
expect(form.getFieldsValue()).toEqual({
279-
username: 'Bamboo',
280-
});
281-
expect(
282-
getField(wrapper, 'username')
283-
.find('input')
284-
.props().value,
285-
).toEqual('Bamboo');
286-
287-
// Should change it
288-
form.resetFields();
289-
wrapper.update();
290-
expect(mountCount).toEqual(1);
291-
expect(form.getFieldsValue()).toEqual({
292-
username: 'Light',
293-
});
294-
expect(
295-
getField(wrapper, 'username')
296-
.find('input')
297-
.props().value,
298-
).toEqual('Light');
299-
});
300-
});
301-
302183
it('should throw if no Form in use', () => {
303184
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
304185

0 commit comments

Comments
 (0)