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 12, 2024
1 parent 8268294 commit 8607178
Show file tree
Hide file tree
Showing 10 changed files with 950 additions and 9 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>
163 changes: 163 additions & 0 deletions docs/examples/scopedForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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({ scope: true });
const college = Form.useWatch([name, 'college'], scopedForm);
const location = Form.useWatch([name, 'location'], { scope: 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>
<button onClick={() => scopedForm.resetFields()}>reset scoped form</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>
);
};
3 changes: 3 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,8 @@ const Context = React.createContext<InternalFormInstance>({
setValidateMessages: warningFunc,
setPreserve: warningFunc,
getInitialValue: warningFunc,
getFieldEntities: warningFunc,
getFormStore: warningFunc,
};
},
});
Expand Down
1 change: 1 addition & 0 deletions src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const Form: React.ForwardRefRenderFunction<FormInstance, FormProps> = (
const formContextValue = React.useMemo(
() => ({
...(formInstance as InternalFormInstance),
formInstance,
validateTrigger,
}),
[formInstance, validateTrigger],
Expand Down
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
13 changes: 12 additions & 1 deletion src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactElement } from 'react';
import type { DeepNamePath } from './namePathType';
import type { ReducerAction } from './useForm';
import type { FormStore, ReducerAction } from './useForm';

export type InternalNamePath = (string | number)[];
export type NamePath<T = any> = DeepNamePath<T>;
Expand Down Expand Up @@ -217,6 +217,12 @@ export type WatchCallBack = (
export interface WatchOptions<Form extends FormInstance = FormInstance> {
form?: Form;
preserve?: boolean;
scope?: boolean;
}

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

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

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

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

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

validateTrigger?: string | string[] | false;

formInstance?: FormInstance;

/**
* Form component should register some content into store.
* We pass the `HOOK_MARK` as key to avoid user call the function.
Expand Down
3 changes: 3 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,8 @@ export class FormStore {
setPreserve: this.setPreserve,
getInitialValue: this.getInitialValue,
registerWatch: this.registerWatch,
getFieldEntities: this.getFieldEntities,
getFormStore: () => this,
};
}

Expand Down
Loading

0 comments on commit 8607178

Please sign in to comment.