Skip to content

Commit affa8d4

Browse files
authored
feat: Support submit & onFormFinish (#21)
* context support onFinish * add test case * update doc
1 parent b822055 commit affa8d4

File tree

8 files changed

+95
-13
lines changed

8 files changed

+95
-13
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class Demo extends React.Component {
130130
| resetFields | Reset fields status | (fields?: [NamePath](#namepath)[]) => void |
131131
| setFields | Set fields status | (fields: FieldData[]) => void |
132132
| setFieldsValue | Set fields value | (values) => void |
133+
| submit | Trigger form submit | () => void |
133134
| validateFields | Trigger fields to validate | (nameList?: [NamePath](#namepath)[], options?: ValidateOptions) => Promise |
134135

135136
## FormProvider
@@ -138,6 +139,7 @@ class Demo extends React.Component {
138139
| ---------------- | ----------------------------------------- | ---------------------------------------- | ------- |
139140
| validateMessages | Config global `validateMessages` template | [ValidateMessages](#validatemessages) | - |
140141
| onFormChange | Trigger by named form fields change | (name, { changedFields, forms }) => void | - |
142+
| onFormFinish | Trigger by named form fields finish | (name, { values, forms }) => void | - |
141143

142144
## Interface
143145

examples/StateForm-context.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const Form2 = () => {
4040
return (
4141
<Form form={form} style={{ ...formStyle, border: '1px solid #F00' }} name="second">
4242
<h4>Form 2</h4>
43-
<p>Will follow Form 1 but not sync back</p>
43+
<p>Will follow Form 1 but sync back only when submit</p>
4444
<LabelField name="username" rules={[{ required: true }]}>
4545
<Input placeholder="username" />
4646
</LabelField>
@@ -66,6 +66,12 @@ const Demo = () => {
6666
forms.second.setFields(changedFields);
6767
}
6868
}}
69+
onFormFinish={(name, { values, forms }) => {
70+
console.log('finish from:', name, values, forms);
71+
if (name === 'second') {
72+
forms.first.setFieldsValue(values);
73+
}
74+
}}
6975
>
7076
<div style={{ display: 'flex', width: '100%' }}>
7177
<Form1 />

src/FieldContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Context = React.createContext<InternalFormInstance>({
2222
setFields: warningFunc,
2323
setFieldsValue: warningFunc,
2424
validateFields: warningFunc,
25+
submit: warningFunc,
2526

2627
getInternalHooks: () => {
2728
warningFunc();

src/Form.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface FormProps extends BaseFormProps {
2727
validateMessages?: ValidateMessages;
2828
onValuesChange?: Callbacks['onValuesChange'];
2929
onFieldsChange?: Callbacks['onFieldsChange'];
30-
onFinish?: (values: Store) => void;
30+
onFinish?: Callbacks['onFinish'];
3131
}
3232

3333
const Form: React.FunctionComponent<FormProps> = (
@@ -83,6 +83,13 @@ const Form: React.FunctionComponent<FormProps> = (
8383
onFieldsChange(changedFields, ...rest);
8484
}
8585
},
86+
onFinish: (values: Store) => {
87+
formContext.triggerFormFinish(name, values);
88+
89+
if (onFinish) {
90+
onFinish(values);
91+
}
92+
},
8693
});
8794

8895
// Set initial value, init store value when first mount
@@ -125,19 +132,11 @@ const Form: React.FunctionComponent<FormProps> = (
125132
return (
126133
<Component
127134
{...restProps}
128-
onSubmit={event => {
135+
onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
129136
event.preventDefault();
130137
event.stopPropagation();
131138

132-
formInstance
133-
.validateFields()
134-
.then(values => {
135-
if (onFinish) {
136-
onFinish(values);
137-
}
138-
})
139-
// Do nothing about submit catch
140-
.catch(e => e);
139+
formInstance.submit();
141140
}}
142141
>
143142
{wrapperNode}

src/FormContext.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { ValidateMessages, FormInstance, FieldData } from './interface';
2+
import { ValidateMessages, FormInstance, FieldData, Store } from './interface';
33

44
interface Forms {
55
[name: string]: FormInstance;
@@ -10,26 +10,35 @@ interface FormChangeInfo {
1010
forms: Forms;
1111
}
1212

13+
interface FormFinishInfo {
14+
values: Store;
15+
forms: Forms;
16+
}
17+
1318
export interface FormProviderProps {
1419
validateMessages?: ValidateMessages;
1520
onFormChange?: (name: string, info: FormChangeInfo) => void;
21+
onFormFinish?: (name: string, info: FormFinishInfo) => void;
1622
}
1723

1824
export interface FormContextProps extends FormProviderProps {
1925
triggerFormChange: (name: string, changedFields: FieldData[]) => void;
26+
triggerFormFinish: (name: string, values: Store) => void;
2027
registerForm: (name: string, form: FormInstance) => void;
2128
unregisterForm: (name: string) => void;
2229
}
2330

2431
const FormContext = React.createContext<FormContextProps>({
2532
triggerFormChange: () => {},
33+
triggerFormFinish: () => {},
2634
registerForm: () => {},
2735
unregisterForm: () => {},
2836
});
2937

3038
const FormProvider: React.FunctionComponent<FormProviderProps> = ({
3139
validateMessages,
3240
onFormChange,
41+
onFormFinish,
3342
children,
3443
}) => {
3544
const formContext = React.useContext(FormContext);
@@ -55,6 +64,16 @@ const FormProvider: React.FunctionComponent<FormProviderProps> = ({
5564

5665
formContext.triggerFormChange(name, changedFields);
5766
},
67+
triggerFormFinish: (name, values) => {
68+
if (onFormFinish) {
69+
onFormFinish(name, {
70+
values,
71+
forms: formsRef.current,
72+
});
73+
}
74+
75+
formContext.triggerFormFinish(name, values);
76+
},
5877
registerForm: (name, form) => {
5978
if (name) {
6079
formsRef.current = {

src/interface.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export type NotifyInfo =
138138
export interface Callbacks {
139139
onValuesChange?: (changedValues: Store, values: Store) => void;
140140
onFieldsChange?: (changedFields: FieldData[], allFields: FieldData[]) => void;
141+
onFinish?: (values: Store) => void;
141142
}
142143

143144
export interface InternalHooks {
@@ -165,6 +166,9 @@ export interface FormInstance {
165166
setFields: (fields: FieldData[]) => void;
166167
setFieldsValue: (value: Store) => void;
167168
validateFields: ValidateFields;
169+
170+
// New API
171+
submit: () => void;
168172
}
169173

170174
export type InternalFormInstance = Omit<FormInstance, 'validateFields'> & {

src/useForm.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class FormStore {
7979
setFields: this.setFields,
8080
setFieldsValue: this.setFieldsValue,
8181
validateFields: this.validateFields,
82+
submit: this.submit,
8283

8384
getInternalHooks: this.getInternalHooks,
8485
});
@@ -503,6 +504,19 @@ export class FormStore {
503504

504505
return returnPromise as Promise<Store>;
505506
};
507+
508+
// ============================ Submit ============================
509+
private submit = () => {
510+
this.validateFields()
511+
.then(values => {
512+
const { onFinish } = this.callbacks;
513+
if (onFinish) {
514+
onFinish(values);
515+
}
516+
})
517+
// Do nothing about submit catch
518+
.catch(e => e);
519+
};
506520
}
507521

508522
function useForm(form?: FormInstance): [FormInstance] {

tests/context.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { mount } from 'enzyme';
33
import Form, { FormProvider } from '../src';
44
import InfoField from './common/InfoField';
55
import { changeValue, matchError, getField } from './common';
6+
import timeout from './common/timeout';
67

78
describe('Form.Context', () => {
89
it('validateMessages', async () => {
@@ -95,6 +96,42 @@ describe('Form.Context', () => {
9596
});
9697
});
9798

99+
it('submit', async () => {
100+
const onFormFinish = jest.fn();
101+
let form1;
102+
103+
const wrapper = mount(
104+
<div>
105+
<FormProvider onFormFinish={onFormFinish}>
106+
<Form
107+
name="form1"
108+
ref={instance => {
109+
form1 = instance;
110+
}}
111+
>
112+
<InfoField name="name" rules={[{ required: true }]} />
113+
</Form>
114+
<Form name="form2" />
115+
</FormProvider>
116+
</div>,
117+
);
118+
119+
await changeValue(getField(wrapper), '');
120+
form1.submit();
121+
await timeout();
122+
expect(onFormFinish).not.toHaveBeenCalled();
123+
124+
await changeValue(getField(wrapper), 'Light');
125+
form1.submit();
126+
await timeout();
127+
expect(onFormFinish).toHaveBeenCalled();
128+
129+
expect(onFormFinish.mock.calls[0][0]).toEqual('form1');
130+
const info = onFormFinish.mock.calls[0][1];
131+
expect(info.values).toEqual({ name: 'Light' });
132+
expect(Object.keys(info.forms).sort()).toEqual(['form1', 'form2'].sort());
133+
});
134+
98135
it('do nothing if no Provider in use', () => {
99136
const wrapper = mount(
100137
<div>

0 commit comments

Comments
 (0)