diff --git a/README.md b/README.md
index ca6c1043..2d2cbd6c 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,19 @@ In `rc-form`, we support like `user.name` to be a name and convert value to `{ u
Field Form will only trade `['user', 'name']` to be `{ user: { name: 'Bamboo' } }`, and `user.name` to be `{ ['user.name']: 'Bamboo' }`.
+## 🔥 Remove `validateFieldsAndScroll`
+
+Since `findDomNode` is marked as warning in [StrictMode](https://reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage). It seems over control of Form component.
+We decide to remove `validateFieldsAndScroll` method and you should handle it with you own logic:
+
+```jsx
+
+```
+
## 🔥 `getFieldsError` always return array
`rc-form` returns `null` when no error happen. This makes user have to do some additional code like:
diff --git a/package.json b/package.json
index 28f2669c..b49415e6 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"compile": "father build",
"gh-pages": "rc-tools run gh-pages",
"pub": "rc-tools run pub --babel-runtime",
- "lint": "eslint src/**/*",
+ "lint": "eslint src/ --ext .tsx,.ts",
"test": "father test",
"now-build": "npm run build"
},
diff --git a/src/Field.tsx b/src/Field.tsx
index 7583d0a9..97c50b8d 100644
--- a/src/Field.tsx
+++ b/src/Field.tsx
@@ -12,6 +12,8 @@ import {
ValidateOptions,
InternalFormInstance,
RuleObject,
+ StoreValue,
+ EventArgs,
} from './interface';
import FieldContext, { HOOK_MARK } from './FieldContext';
import { toArray } from './utils/typeUtil';
@@ -25,10 +27,10 @@ import {
} from './utils/valueUtil';
interface ChildProps {
- value?: any;
- onChange?: (...args: any[]) => void;
- onFocus?: (...args: any[]) => void;
- onBlur?: (...args: any[]) => void;
+ value?: StoreValue;
+ onChange?: (...args: EventArgs) => void;
+ onFocus?: (...args: EventArgs) => void;
+ onBlur?: (...args: EventArgs) => void;
}
export interface FieldProps {
@@ -41,11 +43,11 @@ export interface FieldProps {
* will trigger validate rules and render.
*/
dependencies?: NamePath[];
- getValueFromEvent?: (...args: any[]) => any;
+ getValueFromEvent?: (...args: EventArgs) => StoreValue;
name?: NamePath;
- normalize?: (value: any, prevValue: any, allValues: any) => any;
+ normalize?: (value: StoreValue, prevValue: StoreValue, allValues: Store) => StoreValue;
rules?: Rule[];
- shouldUpdate?: (prevValues: any, nextValues: any, info: { source?: string }) => boolean;
+ shouldUpdate?: (prevValues: Store, nextValues: Store, info: { source?: string }) => boolean;
trigger?: string;
validateTrigger?: string | string[] | false;
}
@@ -77,7 +79,7 @@ class Field extends React.Component implements FieldEnti
*/
private touched: boolean = false;
- private validatePromise: Promise | null = null;
+ private validatePromise: Promise | null = null;
// We reuse the promise to check if is `validating`
private prevErrors: string[];
@@ -149,7 +151,7 @@ class Field extends React.Component implements FieldEnti
// ========================= Field Entity Interfaces =========================
// Trigger by store update. Check if need update the component
public onStoreChange = (
- prevStore: any,
+ prevStore: Store,
namePathList: InternalNamePath[] | null,
info: NotifyInfo,
) => {
@@ -178,7 +180,7 @@ class Field extends React.Component implements FieldEnti
this.touched = data.touched;
}
if ('validating' in data) {
- this.validatePromise = data.validating ? Promise.resolve() : null;
+ this.validatePromise = data.validating ? Promise.resolve([]) : null;
}
this.refresh();
@@ -284,7 +286,7 @@ class Field extends React.Component implements FieldEnti
public getOnlyChild = (
children:
| React.ReactNode
- | ((control: ChildProps, meta: Meta, context: any) => React.ReactNode),
+ | ((control: ChildProps, meta: Meta, context: FormInstance) => React.ReactNode),
): { child: React.ReactElement | null; isFunction: boolean } => {
// Support render props
if (typeof children === 'function') {
@@ -315,10 +317,11 @@ class Field extends React.Component implements FieldEnti
public getControlled = (childProps: ChildProps = {}) => {
const { trigger, validateTrigger, getValueFromEvent, normalize } = this.props;
const namePath = this.getNamePath();
- const { getInternalHooks, validateFields, getFieldsValue }: InternalFormInstance = this.context;
+ const { getInternalHooks, getFieldsValue }: InternalFormInstance = this.context;
const { dispatch } = getInternalHooks(HOOK_MARK);
const value = this.getValue();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const originTriggerFunc: any = childProps[trigger];
const control = {
@@ -327,7 +330,7 @@ class Field extends React.Component implements FieldEnti
};
// Add trigger
- control[trigger] = (...args: any[]) => {
+ control[trigger] = (...args: EventArgs) => {
// Mark as touched
this.touched = true;
@@ -354,7 +357,7 @@ class Field extends React.Component implements FieldEnti
validateTriggerList.forEach((triggerName: string) => {
// Wrap additional function of component, so that we can get latest value from store
const originTrigger = control[triggerName];
- control[triggerName] = (...args: any[]) => {
+ control[triggerName] = (...args: EventArgs) => {
if (originTrigger) {
originTrigger(...args);
}
@@ -364,8 +367,11 @@ class Field extends React.Component implements FieldEnti
if (rules && rules.length) {
// We dispatch validate to root,
// since it will update related data with other field with same name
- // TODO: use dispatch instead
- validateFields([namePath], { triggerName });
+ dispatch({
+ type: 'validateField',
+ namePath,
+ triggerName,
+ });
}
};
});
diff --git a/src/FieldContext.ts b/src/FieldContext.ts
index 11ccc319..ed738ff4 100644
--- a/src/FieldContext.ts
+++ b/src/FieldContext.ts
@@ -4,6 +4,7 @@ import { InternalFormInstance } from './interface';
export const HOOK_MARK = 'RC_FORM_INTERNAL_HOOKS';
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
const warningFunc: any = () => {
warning(false, 'Can not find FormContext. Please make sure you wrap Field under Form.');
};
diff --git a/src/Form.tsx b/src/Form.tsx
index 053dc81d..99ee06d5 100644
--- a/src/Form.tsx
+++ b/src/Form.tsx
@@ -13,10 +13,12 @@ import FormContext, { FormContextProps } from './FormContext';
type BaseFormProps = Omit, 'onSubmit'>;
+type RenderProps = (values: Store, form: FormInstance) => JSX.Element | React.ReactNode;
+
export interface FormProps extends BaseFormProps {
initialValues?: Store;
form?: FormInstance;
- children?: (() => JSX.Element | React.ReactNode) | React.ReactNode;
+ children?: RenderProps | React.ReactNode;
fields?: FieldData[];
name?: string;
validateMessages?: ValidateMessages;
@@ -90,7 +92,7 @@ const Form: React.FunctionComponent = (
const childrenRenderProps = typeof children === 'function';
if (childrenRenderProps) {
const values = formInstance.getFieldsValue();
- childrenNode = (children as any)(values, formInstance);
+ childrenNode = (children as RenderProps)(values, formInstance);
}
// Not use subscribe when using render props
diff --git a/src/List.tsx b/src/List.tsx
index 35ffdbc7..6c136ed7 100644
--- a/src/List.tsx
+++ b/src/List.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import warning from 'warning';
-import { InternalNamePath, NamePath, InternalFormInstance } from './interface';
+import { InternalNamePath, NamePath, InternalFormInstance, StoreValue } from './interface';
import FieldContext, { HOOK_MARK } from './FieldContext';
import Field from './Field';
import { getNamePath, setValue } from './utils/valueUtil';
@@ -21,8 +21,8 @@ interface ListProps {
}
interface ListRenderProps {
- value: any[];
- onChange: (value: any[]) => void;
+ value: StoreValue[];
+ onChange: (value: StoreValue[]) => void;
}
const List: React.FunctionComponent = ({ name, children }) => {
@@ -38,7 +38,7 @@ const List: React.FunctionComponent = ({ name, children }) => {
const parentPrefixName = getNamePath(context.prefixName) || [];
const prefixName: InternalNamePath = [...parentPrefixName, ...getNamePath(name)];
- const shouldUpdate = (prevValue: any, nextValue: any, { source }) => {
+ const shouldUpdate = (prevValue: StoreValue, nextValue: StoreValue, { source }) => {
if (source === 'internal') {
return false;
}
diff --git a/src/index.tsx b/src/index.tsx
index a2f10335..79466f55 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -16,7 +16,7 @@ interface RefForm extends InternalForm {
useForm: typeof useForm;
}
-const RefForm: RefForm = InternalForm as any;
+const RefForm: RefForm = InternalForm as RefForm;
RefForm.FormProvider = FormProvider;
RefForm.Field = Field;
diff --git a/src/interface.ts b/src/interface.ts
index 0a04c605..2048fb5b 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -20,7 +20,7 @@ export interface Meta {
*/
export interface FieldData extends Partial {
name: NamePath;
- value?: any;
+ value?: StoreValue;
}
export type RuleType =
@@ -40,21 +40,21 @@ export type RuleType =
type Validator = (
rule: Rule,
- value: any,
+ value: StoreValue,
callback: (error?: string) => void,
) => Promise | void;
export type RuleRender = (form: FormInstance) => RuleObject;
interface BaseRule {
- enum?: any[];
+ enum?: StoreValue[];
len?: number;
max?: number;
- message?: any;
+ message?: string;
min?: number;
pattern?: RegExp;
required?: boolean;
- transform?: (value: any) => any;
+ transform?: (value: StoreValue) => StoreValue;
type?: RuleType;
validator?: Validator;
whitespace?: boolean;
@@ -79,10 +79,10 @@ export interface ValidateErrorEntity {
}
export interface FieldEntity {
- onStoreChange: (store: any, namePathList: InternalNamePath[] | null, info: NotifyInfo) => void;
+ onStoreChange: (store: Store, namePathList: InternalNamePath[] | null, info: NotifyInfo) => void;
isFieldTouched: () => boolean;
isFieldValidating: () => boolean;
- validateRules: (options?: ValidateOptions) => Promise;
+ validateRules: (options?: ValidateOptions) => Promise;
getMeta: () => Meta;
getNamePath: () => InternalNamePath;
props: {
@@ -105,8 +105,8 @@ export interface ValidateOptions {
export type InternalValidateFields = (
nameList?: NamePath[],
options?: ValidateOptions,
-) => Promise;
-export type ValidateFields = (nameList?: NamePath[]) => Promise;
+) => Promise;
+export type ValidateFields = (nameList?: NamePath[]) => Promise;
export type NotifyInfo =
| {
@@ -144,8 +144,8 @@ export interface InternalHooks {
export interface FormInstance {
// Origin Form API
- getFieldValue: (name: NamePath) => any;
- getFieldsValue: (nameList?: NamePath[]) => any;
+ getFieldValue: (name: NamePath) => StoreValue;
+ getFieldsValue: (nameList?: NamePath[]) => Store;
getFieldError: (name: NamePath) => string[];
getFieldsError: (nameList?: NamePath[]) => FieldError[];
isFieldsTouched: (nameList?: NamePath[]) => boolean;
@@ -173,6 +173,9 @@ export type InternalFormInstance = Omit & {
getInternalHooks: (secret: string) => InternalHooks | null;
};
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type EventArgs = any[];
+
type ValidateMessage = string | (() => string);
export interface ValidateMessages {
default?: ValidateMessage;
diff --git a/src/useForm.ts b/src/useForm.ts
index f1af42bd..a2de4b16 100644
--- a/src/useForm.ts
+++ b/src/useForm.ts
@@ -16,6 +16,7 @@ import {
InternalValidateFields,
InternalFormInstance,
ValidateErrorEntity,
+ StoreValue,
} from './interface';
import { HOOK_MARK } from './FieldContext';
import { allPromiseFinish } from './utils/asyncUtil';
@@ -34,10 +35,16 @@ import {
interface UpdateAction {
type: 'updateValue';
namePath: InternalNamePath;
- value: any;
+ value: StoreValue;
}
-export type ReducerAction = UpdateAction;
+interface ValidateAction {
+ type: 'validateField';
+ namePath: InternalNamePath;
+ triggerName: string;
+}
+
+export type ReducerAction = UpdateAction | ValidateAction;
export class FormStore {
private forceRootUpdate: () => void;
@@ -75,7 +82,6 @@ export class FormStore {
setFields: this.setFields,
setFieldsValue: this.setFieldsValue,
validateFields: this.validateFields,
- // TODO: validateFieldsAndScroll
getInternalHooks: this.getInternalHooks,
});
@@ -294,13 +300,18 @@ export class FormStore {
this.updateValue(namePath, value);
break;
}
+ case 'validateField': {
+ const { namePath, triggerName } = action;
+ this.validateFields([namePath], { triggerName });
+ break;
+ }
default:
// Currently we don't have other action. Do nothing.
}
};
private notifyObservers = (
- prevStore: any,
+ prevStore: Store,
namePathList: InternalNamePath[] | null,
info: NotifyInfo,
) => {
@@ -313,7 +324,7 @@ export class FormStore {
}
};
- private updateValue = (name: NamePath, value: any) => {
+ private updateValue = (name: NamePath, value: StoreValue) => {
const namePath = getNamePath(name);
const prevStore = this.store;
this.store = setValue(this.store, namePath, value);
@@ -341,7 +352,7 @@ export class FormStore {
};
// Let all child Field get update.
- private setFieldsValue = (store: any) => {
+ private setFieldsValue = (store: Store) => {
const prevStore = this.store;
if (store) {
@@ -398,14 +409,13 @@ export class FormStore {
if (onFieldsChange) {
const fields = this.getFields();
const changedFields = fields.filter(({ name: fieldName }) =>
- containsNamePath(namePathList, fieldName as any),
+ containsNamePath(namePathList, fieldName as InternalNamePath),
);
onFieldsChange(changedFields, fields);
}
};
// =========================== Validate ===========================
- // TODO: Cache validate result to avoid duplicated validate???
private validateFields: InternalValidateFields = (
nameList?: NamePath[],
options?: ValidateOptions,
@@ -425,7 +435,10 @@ export class FormStore {
}
// Collect result in promise list
- const promiseList: Promise[] = [];
+ const promiseList: Promise<{
+ name: InternalNamePath;
+ errors: string[];
+ }>[] = [];
this.getFieldEntities().forEach((field: FieldEntity) => {
if (!field.props.rules || !field.props.rules.length) {
@@ -492,12 +505,12 @@ export class FormStore {
// Do not throw in console
returnPromise.catch(e => e);
- return returnPromise as Promise;
+ return returnPromise as Promise;
};
}
function useForm(form?: FormInstance): [FormInstance] {
- const formRef = React.useRef() as any;
+ const formRef = React.useRef();
const [, forceUpdate] = React.useState();
if (!formRef.current) {
diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts
index c5ec0f1b..29420843 100644
--- a/src/utils/valueUtil.ts
+++ b/src/utils/valueUtil.ts
@@ -1,6 +1,6 @@
import setIn from 'lodash/fp/set';
import get from 'lodash/get';
-import { InternalNamePath, NamePath, Store, StoreValue } from '../interface';
+import { InternalNamePath, NamePath, Store, StoreValue, EventArgs } from '../interface';
import { toArray } from './typeUtil';
/**
@@ -112,7 +112,8 @@ export function isSimilar(source: SimilarObject, target: SimilarObject) {
});
}
-export function defaultGetValueFromEvent(event: Event) {
+export function defaultGetValueFromEvent(...args: EventArgs) {
+ const event = args[0];
if (event && event.target && 'value' in event.target) {
return (event.target as HTMLInputElement).value;
}