Skip to content

Commit 8607178

Browse files
committed
feat: support scoped form instance
- add hook Form.useFormInstance - Form.useWatch add param "scoped: true"
1 parent 8268294 commit 8607178

File tree

10 files changed

+950
-9
lines changed

10 files changed

+950
-9
lines changed

docs/demo/scopedForm.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## scopedForm
2+
3+
<code src="../examples/scopedForm.tsx"></code>

docs/examples/scopedForm.tsx

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import React from 'react';
2+
import Form from 'rc-field-form';
3+
import Input from './components/Input';
4+
import { isEqual } from 'lodash';
5+
6+
const ChildrenContent = (props: { name: number }) => {
7+
8+
const { name } = props;
9+
10+
const scopedForm = Form.useFormInstance({ scope: true });
11+
const college = Form.useWatch([name, 'college'], scopedForm);
12+
const location = Form.useWatch([name, 'location'], { scope: true });
13+
const [, forceUpdate] = React.useState({});
14+
15+
React.useEffect(() => {
16+
scopedForm.setFieldValue([name, 'nonexistent'], 'nonexistent');
17+
}, [scopedForm, name]);
18+
19+
return (
20+
<div style={{ marginBottom: 16 }}>
21+
<div>
22+
<Form.Field
23+
name={[name, 'college']}
24+
rules={[
25+
{
26+
required: true,
27+
message: 'college is required',
28+
},
29+
]}
30+
>
31+
<Input placeholder="College" />
32+
</Form.Field>
33+
<span>{college}</span>
34+
</div>
35+
<div>
36+
<Form.Field
37+
name={[name, 'location']}
38+
rules={[
39+
{ required: true, message: 'location is required' },
40+
]}
41+
>
42+
<Input placeholder="Location" />
43+
</Form.Field>
44+
<span>{location}</span>
45+
</div>
46+
<div>
47+
<Form.Field
48+
name={[name, 'field0']}
49+
valuePropName="checked"
50+
>
51+
<input type="checkbox" />
52+
</Form.Field>
53+
Checked
54+
</div>
55+
<div>
56+
<Form.Field
57+
shouldUpdate
58+
>
59+
{
60+
() => {
61+
if (scopedForm.getFieldValue([name, 'field0'])) {
62+
return (
63+
<Form.Field
64+
name={[name, 'field1']}
65+
>
66+
<input type="text" />
67+
</Form.Field>
68+
);
69+
}
70+
return null;
71+
}
72+
}
73+
</Form.Field>
74+
</div>
75+
<div>
76+
<button onClick={() => forceUpdate({})}>forceUpdate</button>
77+
<button onClick={() => scopedForm.resetFields()}>reset scoped form</button>
78+
</div>
79+
<div>
80+
<span>{`scopedForm.getFieldsValue({strict: true }):`}</span>
81+
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true }))}`}</span>
82+
</div>
83+
<div>
84+
<span>scopedForm.getFieldsValue():</span>
85+
<span>{`${JSON.stringify(scopedForm.getFieldsValue())}`}</span>
86+
</div>
87+
<div>
88+
<span>{`scopedForm.getFieldValue([name, 'location']):`}</span>
89+
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'location']))}`}</span>
90+
</div>
91+
<div>
92+
<span>{`scopedForm.getFieldValue([name, 'nonexistent']):`}</span>
93+
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'nonexistent']))}`}</span>
94+
</div>
95+
<div>
96+
<span>{`scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }):`}</span>
97+
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }))}`}</span>
98+
</div>
99+
<div>
100+
<span>{`scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])):`}</span>
101+
<span>{`${JSON.stringify(scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])))}`}</span>
102+
</div>
103+
<div>
104+
<span>{`scopedForm.isFieldsTouched(true):`}</span>
105+
<span>{`${JSON.stringify(scopedForm.isFieldsTouched(true))}`}</span>
106+
</div>
107+
<div>
108+
<span>{`scopedForm.isFieldsTouched():`}</span>
109+
<span>{`${JSON.stringify(scopedForm.isFieldsTouched())}`}</span>
110+
</div>
111+
</div>
112+
);
113+
};
114+
115+
export default () => {
116+
const [form] = Form.useForm();
117+
console.log('rootForm', form);
118+
119+
return (
120+
<div>
121+
<Form
122+
form={form}
123+
initialValues={{
124+
educations: [
125+
{
126+
college: 'Ant Design',
127+
},
128+
],
129+
}}
130+
>
131+
<>
132+
<Form.Field name="name">
133+
<Input placeholder="Name" />
134+
</Form.Field>
135+
<Form.Field name="age">
136+
<Input placeholder="Age" />
137+
</Form.Field>
138+
<Form.List
139+
name="educations"
140+
>
141+
{
142+
(fields, { add }) => (
143+
<div style={{ paddingLeft: 16 }}>
144+
<h2 style={{ marginBottom: 8 }}>Colleges</h2>
145+
{
146+
fields.map(field => {
147+
return (
148+
<ChildrenContent key={field.key} name={field.name} />
149+
);
150+
})
151+
}
152+
<button
153+
onClick={() => add()}
154+
>Add education</button>
155+
</div>
156+
)
157+
}
158+
</Form.List>
159+
</>
160+
</Form>
161+
</div>
162+
);
163+
};

src/FieldContext.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const Context = React.createContext<InternalFormInstance>({
2525
setFieldsValue: warningFunc,
2626
validateFields: warningFunc,
2727
submit: warningFunc,
28+
getScopeName: warningFunc,
2829

2930
getInternalHooks: () => {
3031
warningFunc();
@@ -42,6 +43,8 @@ const Context = React.createContext<InternalFormInstance>({
4243
setValidateMessages: warningFunc,
4344
setPreserve: warningFunc,
4445
getInitialValue: warningFunc,
46+
getFieldEntities: warningFunc,
47+
getFormStore: warningFunc,
4548
};
4649
},
4750
});

src/Form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ const Form: React.ForwardRefRenderFunction<FormInstance, FormProps> = (
142142
const formContextValue = React.useMemo(
143143
() => ({
144144
...(formInstance as InternalFormInstance),
145+
formInstance,
145146
validateTrigger,
146147
}),
147148
[formInstance, validateTrigger],

src/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { FormInstance } from './interface';
2+
import type { FormInstance } from './interface';
33
import Field from './Field';
44
import List from './List';
55
import useForm from './useForm';
@@ -9,6 +9,7 @@ import { FormProvider } from './FormContext';
99
import FieldContext from './FieldContext';
1010
import ListContext from './ListContext';
1111
import useWatch from './useWatch';
12+
import useFormInstance from './useFormInstance';
1213

1314
const InternalForm = React.forwardRef<FormInstance, FormProps>(FieldForm) as <Values = any>(
1415
props: FormProps<Values> & { ref?: React.Ref<FormInstance<Values>> },
@@ -21,6 +22,7 @@ interface RefFormType extends InternalFormType {
2122
List: typeof List;
2223
useForm: typeof useForm;
2324
useWatch: typeof useWatch;
25+
useFormInstance: typeof useFormInstance;
2426
}
2527

2628
const RefForm: RefFormType = InternalForm as RefFormType;
@@ -30,8 +32,9 @@ RefForm.Field = Field;
3032
RefForm.List = List;
3133
RefForm.useForm = useForm;
3234
RefForm.useWatch = useWatch;
35+
RefForm.useFormInstance = useFormInstance;
3336

34-
export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch };
37+
export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch, useFormInstance };
3538

3639
export type { FormProps, FormInstance };
3740

src/interface.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ReactElement } from 'react';
22
import type { DeepNamePath } from './namePathType';
3-
import type { ReducerAction } from './useForm';
3+
import type { FormStore, ReducerAction } from './useForm';
44

55
export type InternalNamePath = (string | number)[];
66
export type NamePath<T = any> = DeepNamePath<T>;
@@ -217,6 +217,12 @@ export type WatchCallBack = (
217217
export interface WatchOptions<Form extends FormInstance = FormInstance> {
218218
form?: Form;
219219
preserve?: boolean;
220+
scope?: boolean;
221+
}
222+
223+
export interface FormInstanceOptions<Form extends FormInstance = FormInstance> {
224+
form?: Form;
225+
scope?: boolean;
220226
}
221227

222228
export interface InternalHooks {
@@ -232,6 +238,8 @@ export interface InternalHooks {
232238
setValidateMessages: (validateMessages: ValidateMessages) => void;
233239
setPreserve: (preserve?: boolean) => void;
234240
getInitialValue: (namePath: InternalNamePath) => StoreValue;
241+
getFieldEntities: (prue: boolean) => FieldEntity[];
242+
getFormStore: () => FormStore;
235243
}
236244

237245
/** Only return partial when type is not any */
@@ -271,6 +279,7 @@ export interface FormInstance<Values = any> {
271279

272280
// New API
273281
submit: () => void;
282+
getScopeName: () => InternalNamePath | undefined;
274283
}
275284

276285
export type InternalFormInstance = Omit<FormInstance, 'validateFields'> & {
@@ -283,6 +292,8 @@ export type InternalFormInstance = Omit<FormInstance, 'validateFields'> & {
283292

284293
validateTrigger?: string | string[] | false;
285294

295+
formInstance?: FormInstance;
296+
286297
/**
287298
* Form component should register some content into store.
288299
* We pass the `HOOK_MARK` as key to avoid user call the function.

src/useForm.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class FormStore {
9696
setFieldsValue: this.setFieldsValue,
9797
validateFields: this.validateFields,
9898
submit: this.submit,
99+
getScopeName: () => undefined,
99100
_init: true,
100101

101102
getInternalHooks: this.getInternalHooks,
@@ -119,6 +120,8 @@ export class FormStore {
119120
setPreserve: this.setPreserve,
120121
getInitialValue: this.getInitialValue,
121122
registerWatch: this.registerWatch,
123+
getFieldEntities: this.getFieldEntities,
124+
getFormStore: () => this,
122125
};
123126
}
124127

0 commit comments

Comments
 (0)