Skip to content

Commit

Permalink
feat: support scoped form instance
Browse files Browse the repository at this point in the history
- add hook Form.useFormInstance
- Form.useWatch add param "scoped: true"
  • Loading branch information
lemonied committed Mar 11, 2024
1 parent 8268294 commit 8cecf25
Show file tree
Hide file tree
Showing 11 changed files with 954 additions and 11 deletions.
3 changes: 3 additions & 0 deletions docs/demo/scopedForm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## scopedForm

<code src="../examples/scopedForm.tsx"></code>
162 changes: 162 additions & 0 deletions docs/examples/scopedForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
import Form from 'rc-field-form';
import Input from './components/Input';
import { isEqual } from 'lodash';

const ChildrenContent = (props: { name: number }) => {

const { name } = props;

const scopedForm = Form.useFormInstance({ scoped: true });
const college = Form.useWatch([name, 'college'], scopedForm);
const location = Form.useWatch([name, 'location'], { scoped: true });
const [, forceUpdate] = React.useState({});

React.useEffect(() => {
scopedForm.setFieldValue([name, 'nonexistent'], 'nonexistent');
}, [scopedForm, name]);

return (
<div style={{ marginBottom: 16 }}>
<div>
<Form.Field
name={[name, 'college']}
rules={[
{
required: true,
message: 'college is required',
},
]}
>
<Input placeholder="College" />
</Form.Field>
<span>{college}</span>
</div>
<div>
<Form.Field
name={[name, 'location']}
rules={[
{ required: true, message: 'location is required' },
]}
>
<Input placeholder="Location" />
</Form.Field>
<span>{location}</span>
</div>
<div>
<Form.Field
name={[name, 'field0']}
valuePropName="checked"
>
<input type="checkbox" />
</Form.Field>
Checked
</div>
<div>
<Form.Field
shouldUpdate
>
{
() => {
if (scopedForm.getFieldValue([name, 'field0'])) {
return (
<Form.Field
name={[name, 'field1']}
>
<input type="text" />
</Form.Field>
);
}
return null;
}
}
</Form.Field>
</div>
<div>
<button onClick={() => forceUpdate({})}>forceUpdate</button>
</div>
<div>
<span>{`scopedForm.getFieldsValue({strict: true }):`}</span>
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true }))}`}</span>
</div>
<div>
<span>scopedForm.getFieldsValue():</span>
<span>{`${JSON.stringify(scopedForm.getFieldsValue())}`}</span>
</div>
<div>
<span>{`scopedForm.getFieldValue([name, 'location']):`}</span>
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'location']))}`}</span>
</div>
<div>
<span>{`scopedForm.getFieldValue([name, 'nonexistent']):`}</span>
<span>{`${JSON.stringify(scopedForm.getFieldValue([name, 'nonexistent']))}`}</span>
</div>
<div>
<span>{`scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }):`}</span>
<span>{`${JSON.stringify(scopedForm.getFieldsValue({ strict: true, filter: meta => isEqual(meta.name, [name, 'location']) }))}`}</span>
</div>
<div>
<span>{`scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])):`}</span>
<span>{`${JSON.stringify(scopedForm.getFieldsValue(true, meta => isEqual(meta.name, [name, 'location'])))}`}</span>
</div>
<div>
<span>{`scopedForm.isFieldsTouched(true):`}</span>
<span>{`${JSON.stringify(scopedForm.isFieldsTouched(true))}`}</span>
</div>
<div>
<span>{`scopedForm.isFieldsTouched():`}</span>
<span>{`${JSON.stringify(scopedForm.isFieldsTouched())}`}</span>
</div>
</div>
);
};

export default () => {
const [form] = Form.useForm();
console.log('rootForm', form);

return (
<div>
<Form
form={form}
initialValues={{
educations: [
{
college: 'Ant Design',
},
],
}}
>
<>
<Form.Field name="name">
<Input placeholder="Name" />
</Form.Field>
<Form.Field name="age">
<Input placeholder="Age" />
</Form.Field>
<Form.List
name={'educations'}
>
{
(fields, { add }) => (
<div style={{ paddingLeft: 16 }}>
<h2 style={{ marginBottom: 8 }}>Colleges</h2>
{
fields.map(field => {
return (
<ChildrenContent key={field.key} name={field.name} />
);
})
}
<button
onClick={() => add()}
>Add education</button>
</div>
)
}
</Form.List>
</>
</Form>
</div>
);
};
2 changes: 2 additions & 0 deletions src/FieldContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const Context = React.createContext<InternalFormInstance>({
setFieldsValue: warningFunc,
validateFields: warningFunc,
submit: warningFunc,
getScopeName: warningFunc,

getInternalHooks: () => {
warningFunc();
Expand All @@ -42,6 +43,7 @@ const Context = React.createContext<InternalFormInstance>({
setValidateMessages: warningFunc,
setPreserve: warningFunc,
getInitialValue: warningFunc,
getFieldEntities: warningFunc,
};
},
});
Expand Down
9 changes: 6 additions & 3 deletions src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { FormContextProps } from './FormContext';
import FormContext from './FormContext';
import { isSimilar } from './utils/valueUtil';
import ListContext from './ListContext';
import FormInstanceContext from './FormInstanceContext';

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

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

const wrapperNode = (
<ListContext.Provider value={null}>
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
</ListContext.Provider>
<FormInstanceContext.Provider value={formInstance as InternalFormInstance}>
<ListContext.Provider value={null}>
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
</ListContext.Provider>
</FormInstanceContext.Provider>
);

if (Component === false) {
Expand Down
6 changes: 6 additions & 0 deletions src/FormInstanceContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react';
import type { InternalFormInstance } from './interface';

const FormInstanceContext = React.createContext<InternalFormInstance | undefined>(undefined);

export default FormInstanceContext;
7 changes: 5 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { FormInstance } from './interface';
import type { FormInstance } from './interface';
import Field from './Field';
import List from './List';
import useForm from './useForm';
Expand All @@ -9,6 +9,7 @@ import { FormProvider } from './FormContext';
import FieldContext from './FieldContext';
import ListContext from './ListContext';
import useWatch from './useWatch';
import useFormInstance from './useFormInstance';

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

const RefForm: RefFormType = InternalForm as RefFormType;
Expand All @@ -30,8 +32,9 @@ RefForm.Field = Field;
RefForm.List = List;
RefForm.useForm = useForm;
RefForm.useWatch = useWatch;
RefForm.useFormInstance = useFormInstance;

export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch };
export { Field, List, useForm, FormProvider, FieldContext, ListContext, useWatch, useFormInstance };

export type { FormProps, FormInstance };

Expand Down
8 changes: 8 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ export type WatchCallBack = (
export interface WatchOptions<Form extends FormInstance = FormInstance> {
form?: Form;
preserve?: boolean;
scoped?: boolean;
}

export interface FormInstanceOptions<Form extends FormInstance = FormInstance> {
form?: Form;
scoped?: boolean;
}

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

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

// New API
submit: () => void;
getScopeName: () => InternalNamePath | undefined;
}

export type InternalFormInstance = Omit<FormInstance, 'validateFields'> & {
Expand Down
2 changes: 2 additions & 0 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class FormStore {
setFieldsValue: this.setFieldsValue,
validateFields: this.validateFields,
submit: this.submit,
getScopeName: () => undefined,
_init: true,

getInternalHooks: this.getInternalHooks,
Expand All @@ -119,6 +120,7 @@ export class FormStore {
setPreserve: this.setPreserve,
getInitialValue: this.getInitialValue,
registerWatch: this.registerWatch,
getFieldEntities: this.getFieldEntities,
};
}

Expand Down
Loading

0 comments on commit 8cecf25

Please sign in to comment.