Skip to content

Commit 62c59d7

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

File tree

11 files changed

+954
-11
lines changed

11 files changed

+954
-11
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: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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({ scoped: true });
11+
const college = Form.useWatch([name, 'college'], scopedForm);
12+
const location = Form.useWatch([name, 'location'], { scoped: 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+
</div>
78+
<div>
79+
<span>{`scopedForm.getFieldsValue({strict: true }):`}</span>
80+
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true }))}`}</span>
81+
</div>
82+
<div>
83+
<span>scopedForm.getFieldsValue():</span>
84+
<span>{`${JSON.stringify(scopedForm.getFieldsValue())}`}</span>
85+
</div>
86+
<div>
87+
<span>{`scopedForm.getFieldValue([name, 'location']):`}</span>
88+
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'location']))}`}</span>
89+
</div>
90+
<div>
91+
<span>{`scopedForm.getFieldValue([name, 'nonexistent']):`}</span>
92+
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'nonexistent']))}`}</span>
93+
</div>
94+
<div>
95+
<span>{`scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }):`}</span>
96+
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }))}`}</span>
97+
</div>
98+
<div>
99+
<span>{`scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])):`}</span>
100+
<span>{`${JSON.stringify(scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])))}`}</span>
101+
</div>
102+
<div>
103+
<span>{`scopedForm.isFieldsTouched(true):`}</span>
104+
<span>{`${JSON.stringify(scopedForm.isFieldsTouched(true))}`}</span>
105+
</div>
106+
<div>
107+
<span>{`scopedForm.isFieldsTouched():`}</span>
108+
<span>{`${JSON.stringify(scopedForm.isFieldsTouched())}`}</span>
109+
</div>
110+
</div>
111+
);
112+
};
113+
114+
export default () => {
115+
const [form] = Form.useForm();
116+
console.log('rootForm', form);
117+
118+
return (
119+
<div>
120+
<Form
121+
form={form}
122+
initialValues={{
123+
educations: [
124+
{
125+
college: 'Ant Design',
126+
},
127+
],
128+
}}
129+
>
130+
<>
131+
<Form.Field name="name">
132+
<Input placeholder="Name" />
133+
</Form.Field>
134+
<Form.Field name="age">
135+
<Input placeholder="Age" />
136+
</Form.Field>
137+
<Form.List
138+
name="educations"
139+
>
140+
{
141+
(fields, { add }) => (
142+
<div style={{ paddingLeft: 16 }}>
143+
<h2 style={{ marginBottom: 8 }}>Colleges</h2>
144+
{
145+
fields.map(field => {
146+
return (
147+
<ChildrenContent key={field.key} name={field.name} />
148+
);
149+
})
150+
}
151+
<button
152+
onClick={() => add()}
153+
>Add education</button>
154+
</div>
155+
)
156+
}
157+
</Form.List>
158+
</>
159+
</Form>
160+
</div>
161+
);
162+
};

src/FieldContext.ts

Lines changed: 2 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,7 @@ const Context = React.createContext<InternalFormInstance>({
4243
setValidateMessages: warningFunc,
4344
setPreserve: warningFunc,
4445
getInitialValue: warningFunc,
46+
getFieldEntities: warningFunc,
4547
};
4648
},
4749
});

src/Form.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { FormContextProps } from './FormContext';
1313
import FormContext from './FormContext';
1414
import { isSimilar } from './utils/valueUtil';
1515
import ListContext from './ListContext';
16+
import FormInstanceContext from './FormInstanceContext';
1617

1718
type BaseFormProps = Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'children'>;
1819

@@ -148,9 +149,11 @@ const Form: React.ForwardRefRenderFunction<FormInstance, FormProps> = (
148149
);
149150

150151
const wrapperNode = (
151-
<ListContext.Provider value={null}>
152-
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
153-
</ListContext.Provider>
152+
<FormInstanceContext.Provider value={formInstance as InternalFormInstance}>
153+
<ListContext.Provider value={null}>
154+
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
155+
</ListContext.Provider>
156+
</FormInstanceContext.Provider>
154157
);
155158

156159
if (Component === false) {

src/FormInstanceContext.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as React from 'react';
2+
import type { InternalFormInstance } from './interface';
3+
4+
const FormInstanceContext = React.createContext<InternalFormInstance | undefined>(undefined);
5+
6+
export default FormInstanceContext;

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ export type WatchCallBack = (
217217
export interface WatchOptions<Form extends FormInstance = FormInstance> {
218218
form?: Form;
219219
preserve?: boolean;
220+
scoped?: boolean;
221+
}
222+
223+
export interface FormInstanceOptions<Form extends FormInstance = FormInstance> {
224+
form?: Form;
225+
scoped?: boolean;
220226
}
221227

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

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

272279
// New API
273280
submit: () => void;
281+
getScopeName: () => InternalNamePath | undefined;
274282
}
275283

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

src/useForm.ts

Lines changed: 2 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,7 @@ export class FormStore {
119120
setPreserve: this.setPreserve,
120121
getInitialValue: this.getInitialValue,
121122
registerWatch: this.registerWatch,
123+
getFieldEntities: this.getFieldEntities,
122124
};
123125
}
124126

0 commit comments

Comments
 (0)