From faa37f41560dcca35e3e82de5c791ffe10a3b090 Mon Sep 17 00:00:00 2001 From: zombiej Date: Thu, 20 Jun 2019 12:19:56 +0800 Subject: [PATCH 1/2] remove todo --- README.md | 13 +++++++++++++ src/Field.tsx | 7 +++++-- src/useForm.ts | 15 ++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) 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/src/Field.tsx b/src/Field.tsx index 7583d0a9..7b777a70 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -364,8 +364,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/useForm.ts b/src/useForm.ts index f1af42bd..3e5b423a 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -37,7 +37,13 @@ interface UpdateAction { value: any; } -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 +81,6 @@ export class FormStore { setFields: this.setFields, setFieldsValue: this.setFieldsValue, validateFields: this.validateFields, - // TODO: validateFieldsAndScroll getInternalHooks: this.getInternalHooks, }); @@ -294,6 +299,11 @@ 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. } @@ -405,7 +415,6 @@ export class FormStore { }; // =========================== Validate =========================== - // TODO: Cache validate result to avoid duplicated validate??? private validateFields: InternalValidateFields = ( nameList?: NamePath[], options?: ValidateOptions, From 85e7f31309bf55eb0b9e82659188d4101400c161 Mon Sep 17 00:00:00 2001 From: zombiej Date: Thu, 20 Jun 2019 14:12:28 +0800 Subject: [PATCH 2/2] fix lint --- package.json | 2 +- src/Field.tsx | 31 +++++++++++++++++-------------- src/FieldContext.ts | 1 + src/Form.tsx | 6 ++++-- src/List.tsx | 8 ++++---- src/index.tsx | 2 +- src/interface.ts | 25 ++++++++++++++----------- src/useForm.ts | 20 ++++++++++++-------- src/utils/valueUtil.ts | 5 +++-- 9 files changed, 57 insertions(+), 43 deletions(-) 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 7b777a70..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); } 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 3e5b423a..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,7 +35,7 @@ import { interface UpdateAction { type: 'updateValue'; namePath: InternalNamePath; - value: any; + value: StoreValue; } interface ValidateAction { @@ -310,7 +311,7 @@ export class FormStore { }; private notifyObservers = ( - prevStore: any, + prevStore: Store, namePathList: InternalNamePath[] | null, info: NotifyInfo, ) => { @@ -323,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); @@ -351,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) { @@ -408,7 +409,7 @@ 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); } @@ -434,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) { @@ -501,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; }