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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ We use typescript to create the Type definition. You can view directly in IDE. B
| dependencies | Will re-render if dependencies changed | [NamePath](#namepath)[] | - |
| getValueFromEvent | Specify how to get value from event | (..args: any[]) => any | - |
| getValueProps | Customize additional props with value. This prop will disable `valuePropName` | (value) => any | - |
| initialValue | Field initial value | any | - |
| name | Field name path | [NamePath](#namepath) | - |
| normalize | Normalize value before update | (value, prevValue, prevValues) => any | - |
| rules | Validate rules | [Rule](#rule)[] | - |
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
"react": "*"
},
"devDependencies": {
"@types/jest": "^25.2.1",
"@types/lodash": "^4.14.135",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@types/warning": "^3.0.0",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"enzyme-to-json": "^3.1.4",
Expand All @@ -63,7 +63,6 @@
"dependencies": {
"@babel/runtime": "^7.8.4",
"async-validator": "^3.0.3",
"rc-util": "^4.17.0",
"warning": "^4.0.3"
"rc-util": "^4.20.3"
}
}
1 change: 1 addition & 0 deletions src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface InternalFieldProps {
valuePropName?: string;
getValueProps?: (value: StoreValue) => object;
messageVariables?: Record<string, string>;
initialValue?: any;
onReset?: () => void;
}

Expand Down
2 changes: 1 addition & 1 deletion src/FieldContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import warning from 'warning';
import warning from 'rc-util/lib/warning';
import { InternalFormInstance } from './interface';

export const HOOK_MARK = 'RC_FORM_INTERNAL_HOOKS';
Expand Down
2 changes: 1 addition & 1 deletion src/List.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import warning from 'warning';
import warning from 'rc-util/lib/warning';
import { InternalNamePath, NamePath, StoreValue } from './interface';
import FieldContext from './FieldContext';
import Field from './Field';
Expand Down
1 change: 1 addition & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface FieldEntity {
name?: NamePath;
rules?: Rule[];
dependencies?: NamePath[];
initialValue?: any;
};
}

Expand Down
102 changes: 101 additions & 1 deletion src/useForm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import warning from 'warning';
import warning from 'rc-util/lib/warning';
import {
Callbacks,
FieldData,
Expand Down Expand Up @@ -308,12 +308,100 @@ export class FormStore {
return this.isFieldsValidating([name]);
};

/**
* Reset Field with field `initialValue` prop.
* Can pass `entities` or `namePathList` or just nothing.
*/
private resetWithFieldInitialValue = (
info: {
entities?: FieldEntity[];
namePathList?: InternalNamePath[];
/** Skip reset if store exist value. This is only used for field register reset */
skipExist?: boolean;
} = {},
) => {
// Create cache
const cache: NameMap<Set<{ entity: FieldEntity; value: any }>> = new NameMap();

const fieldEntities = this.getFieldEntities(true);
fieldEntities.forEach(field => {
const { initialValue } = field.props;
const namePath = field.getNamePath();

// Record only if has `initialValue`
if (initialValue !== undefined) {
const records = cache.get(namePath) || new Set();
records.add({ entity: field, value: initialValue });

cache.set(namePath, records);
}
});

// Reset
const resetWithFields = (entities: FieldEntity[]) => {
entities.forEach(field => {
const { initialValue } = field.props;

if (initialValue !== undefined) {
const namePath = field.getNamePath();
const formInitialValue = this.getInitialValue(namePath);

if (formInitialValue !== undefined) {
// Warning if conflict with form initialValues and do not modify value
warning(
false,
`Form already set 'initialValues' with path '${namePath.join(
'.',
)}'. Field can not overwrite it.`,
);
} else {
const records = cache.get(namePath);
if (records && records.size > 1) {
// Warning if multiple field set `initialValue`and do not modify value
warning(
false,
`Multiple Field with path '${namePath.join(
'.',
)}' set 'initialValue'. Can not decide which one to pick.`,
);
} else if (records) {
const originValue = this.getFieldValue(namePath);
// Set `initialValue`
if (!info.skipExist || originValue === undefined) {
this.store = setValue(this.store, namePath, [...records][0].value);
}
}
}
}
});
};

let requiredFieldEntities: FieldEntity[];
if (info.entities) {
requiredFieldEntities = info.entities;
} else if (info.namePathList) {
requiredFieldEntities = [];

info.namePathList.forEach(namePath => {
const records = cache.get(namePath);
if (records) {
requiredFieldEntities.push(...[...records].map(r => r.entity));
}
});
} else {
requiredFieldEntities = fieldEntities;
}

resetWithFields(requiredFieldEntities);
};

private resetFields = (nameList?: NamePath[]) => {
this.warningUnhooked();

const prevStore = this.store;
if (!nameList) {
this.store = setValues({}, this.initialValues);
this.resetWithFieldInitialValue();
this.notifyObservers(prevStore, null, { type: 'reset' });
return;
}
Expand All @@ -324,6 +412,7 @@ export class FormStore {
const initialValue = this.getInitialValue(namePath);
this.store = setValue(this.store, namePath, initialValue);
});
this.resetWithFieldInitialValue({ namePathList });
this.notifyObservers(prevStore, namePathList, { type: 'reset' });
};

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

// Set initial values
if (entity.props.initialValue !== undefined) {
const prevStore = this.store;
this.resetWithFieldInitialValue({ entities: [entity], skipExist: true });
this.notifyObservers(prevStore, [entity.getNamePath()], {
type: 'valueUpdate',
source: 'internal',
});
}

// un-register field callback
return () => {
this.fieldEntities = this.fieldEntities.filter(item => item !== entity);
};
Expand Down
2 changes: 1 addition & 1 deletion src/utils/validateUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import RawAsyncValidator from 'async-validator';
import * as React from 'react';
import warning from 'warning';
import warning from 'rc-util/lib/warning';
import {
InternalNamePath,
ValidateOptions,
Expand Down
119 changes: 0 additions & 119 deletions tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,125 +180,6 @@ describe('Form.Basic', () => {
});
});

describe('initialValues', () => {
it('works', () => {
let form;

const wrapper = mount(
<div>
<Form
ref={instance => {
form = instance;
}}
initialValues={{ username: 'Light', path1: { path2: 'Bamboo' } }}
>
<Field name="username">
<Input />
</Field>
<Field name={['path1', 'path2']}>
<Input />
</Field>
</Form>
</div>,
);

expect(form.getFieldsValue()).toEqual({
username: 'Light',
path1: {
path2: 'Bamboo',
},
});
expect(form.getFieldsValue(['username'])).toEqual({
username: 'Light',
});
expect(form.getFieldsValue(['path1'])).toEqual({
path1: {
path2: 'Bamboo',
},
});
expect(form.getFieldsValue(['username', ['path1', 'path2']])).toEqual({
username: 'Light',
path1: {
path2: 'Bamboo',
},
});
expect(
getField(wrapper, 'username')
.find('input')
.props().value,
).toEqual('Light');
expect(
getField(wrapper, ['path1', 'path2'])
.find('input')
.props().value,
).toEqual('Bamboo');
});

it('update and reset should use new initialValues', () => {
let form;
let mountCount = 0;

const TestInput = props => {
React.useEffect(() => {
mountCount += 1;
}, []);

return <Input {...props} />;
};

const Test = ({ initialValues }) => (
<Form
ref={instance => {
form = instance;
}}
initialValues={initialValues}
>
<Field name="username">
<Input />
</Field>
<Field name="email">
<TestInput />
</Field>
</Form>
);

const wrapper = mount(<Test initialValues={{ username: 'Bamboo' }} />);
expect(form.getFieldsValue()).toEqual({
username: 'Bamboo',
});
expect(
getField(wrapper, 'username')
.find('input')
.props().value,
).toEqual('Bamboo');

// Should not change it
wrapper.setProps({ initialValues: { username: 'Light' } });
wrapper.update();
expect(form.getFieldsValue()).toEqual({
username: 'Bamboo',
});
expect(
getField(wrapper, 'username')
.find('input')
.props().value,
).toEqual('Bamboo');

// Should change it
form.resetFields();
wrapper.update();
expect(mountCount).toEqual(1);
expect(form.getFieldsValue()).toEqual({
username: 'Light',
});
expect(
getField(wrapper, 'username')
.find('input')
.props().value,
).toEqual('Light');
});
});

it('should throw if no Form in use', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

Expand Down
Loading