Skip to content

Commit 1a1aedb

Browse files
authored
feat: Form & FormInstance support generic (#172)
* feat: Form support generic * update example * setFieldsValue should be a type to extend * useForm support generic * Form auto FormInstance types
1 parent 7e7f97e commit 1a1aedb

File tree

6 files changed

+59
-21
lines changed

6 files changed

+59
-21
lines changed

examples/basic.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import React from 'react';
2-
import Form, { Field } from '../src';
2+
import Form, { Field, FormInstance } from '../src';
33
import Input from './components/Input';
44

55
const list = new Array(1111).fill(() => null);
66

7+
interface FormValues {
8+
username?: string;
9+
password?: string;
10+
path1?: {
11+
path2?: string;
12+
};
13+
}
14+
715
export default class Demo extends React.Component {
8-
public state = {};
16+
formRef = React.createRef<FormInstance<FormValues>>();
17+
18+
onFinish = (values: FormValues) => {
19+
console.log('Submit:', values);
20+
21+
setTimeout(() => {
22+
this.formRef.current.setFieldsValue({ path1: { path2: '2333' } });
23+
}, 500);
24+
};
925

1026
public render() {
1127
return (
1228
<div>
1329
<h3>State Form ({list.length} inputs)</h3>
14-
<Form>
30+
<Form<FormValues> ref={this.formRef} onFinish={this.onFinish}>
1531
<Field name="username">
1632
<Input placeholder="Username" />
1733
</Field>
@@ -33,6 +49,8 @@ export default class Demo extends React.Component {
3349
)}
3450
</Field>
3551

52+
<button type="submit">Submit</button>
53+
3654
<h4>Show additional field when `username` is `111`</h4>
3755
<Field dependencies={['username']}>
3856
{(control, meta, context) => {

examples/useForm.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ const { Field, useForm } = Form;
66

77
const list = new Array(0).fill(() => undefined);
88

9+
interface FormValues {
10+
username?: string;
11+
password?: string;
12+
path1?: {
13+
path2?: string;
14+
};
15+
}
16+
917
export default () => {
10-
const [form] = useForm();
18+
const [form] = useForm<FormValues>();
1119

1220
return (
1321
<div>

src/Form.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ type BaseFormProps = Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onSubmit'>
1616

1717
type RenderProps = (values: Store, form: FormInstance) => JSX.Element | React.ReactNode;
1818

19-
export interface FormProps extends BaseFormProps {
19+
export interface FormProps<Values = any> extends BaseFormProps {
2020
initialValues?: Store;
21-
form?: FormInstance;
21+
form?: FormInstance<Values>;
2222
children?: RenderProps | React.ReactNode;
2323
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2424
component?: false | string | React.FC<any> | React.ComponentClass<any>;
2525
fields?: FieldData[];
2626
name?: string;
2727
validateMessages?: ValidateMessages;
28-
onValuesChange?: Callbacks['onValuesChange'];
29-
onFieldsChange?: Callbacks['onFieldsChange'];
30-
onFinish?: Callbacks['onFinish'];
31-
onFinishFailed?: Callbacks['onFinishFailed'];
28+
onValuesChange?: Callbacks<Values>['onValuesChange'];
29+
onFieldsChange?: Callbacks<Values>['onFieldsChange'];
30+
onFinish?: Callbacks<Values>['onFinish'];
31+
onFinishFailed?: Callbacks<Values>['onFinishFailed'];
3232
validateTrigger?: string | string[] | false;
3333
preserve?: boolean;
3434
}

src/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import useForm from './useForm';
66
import FieldForm, { FormProps } from './Form';
77
import { FormProvider } from './FormContext';
88

9-
const InternalForm = React.forwardRef<FormInstance, FormProps>(FieldForm);
9+
const InternalForm = React.forwardRef<FormInstance, FormProps>(FieldForm) as <Values = any>(
10+
props: React.PropsWithChildren<FormProps<Values>> & { ref?: React.Ref<FormInstance> },
11+
) => React.ReactElement;
1012

1113
type InternalForm = typeof InternalForm;
1214
interface RefForm extends InternalForm {

src/interface.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export type RuleObject = BaseRule | ArrayRule;
7676

7777
export type Rule = RuleObject | RuleRender;
7878

79-
export interface ValidateErrorEntity {
80-
values: Store;
79+
export interface ValidateErrorEntity<Values = any> {
80+
values: Values;
8181
errorFields: { name: InternalNamePath; errors: string[] }[];
8282
outOfDate: boolean;
8383
}
@@ -159,11 +159,11 @@ export type ValuedNotifyInfo = NotifyInfo & {
159159
store: Store;
160160
};
161161

162-
export interface Callbacks {
163-
onValuesChange?: (changedValues: Store, values: Store) => void;
162+
export interface Callbacks<Values = any> {
163+
onValuesChange?: (changedValues: any, values: Values) => void;
164164
onFieldsChange?: (changedFields: FieldData[], allFields: FieldData[]) => void;
165-
onFinish?: (values: Store) => void;
166-
onFinishFailed?: (errorInfo: ValidateErrorEntity) => void;
165+
onFinish?: (values: Values) => void;
166+
onFinishFailed?: (errorInfo: ValidateErrorEntity<Values>) => void;
167167
}
168168

169169
export interface InternalHooks {
@@ -177,10 +177,20 @@ export interface InternalHooks {
177177
setPreserve: (preserve?: boolean) => void;
178178
}
179179

180-
export interface FormInstance {
180+
/** Only return partial when type is not any */
181+
type RecursivePartial<T> = T extends object
182+
? {
183+
[P in keyof T]?: RecursivePartial<T[P]>;
184+
}
185+
: any;
186+
187+
export interface FormInstance<Values = any> {
181188
// Origin Form API
182189
getFieldValue: (name: NamePath) => StoreValue;
183-
getFieldsValue: (nameList?: NamePath[] | true, filterFunc?: (meta: Meta) => boolean) => Store;
190+
getFieldsValue: (
191+
nameList?: NamePath[] | true,
192+
filterFunc?: (meta: Meta) => boolean,
193+
) => Values | any;
184194
getFieldError: (name: NamePath) => string[];
185195
getFieldsError: (nameList?: NamePath[]) => FieldError[];
186196
isFieldsTouched(nameList?: NamePath[], allFieldsTouched?: boolean): boolean;
@@ -190,7 +200,7 @@ export interface FormInstance {
190200
isFieldsValidating: (nameList: NamePath[]) => boolean;
191201
resetFields: (fields?: NamePath[]) => void;
192202
setFields: (fields: FieldData[]) => void;
193-
setFieldsValue: (value: Store) => void;
203+
setFieldsValue: (value: RecursivePartial<Values>) => void;
194204
validateFields: ValidateFields;
195205

196206
// New API

src/useForm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ export class FormStore {
778778
};
779779
}
780780

781-
function useForm(form?: FormInstance): [FormInstance] {
781+
function useForm<Values = any>(form?: FormInstance<Values>): [FormInstance<Values>] {
782782
const formRef = React.useRef<FormInstance>();
783783
const [, forceUpdate] = React.useState();
784784

0 commit comments

Comments
 (0)