Browse Source

feat: form add onErrorChange (#2484)

* feat: form add onErrorChange
pointhalo 1 year ago
parent
commit
a614499247

+ 2 - 1
content/input/form/index-en-US.md

@@ -2068,7 +2068,8 @@ render(WithFieldDemo2);
 | labelPosition     | Location of label in Field, optional 'top', 'left', 'inset' <br/> (inset label only partial component support)                                                                                                                                                                                                      | string                                          | 'top'      |
 | labelWidth        | Width of field'r label                                                                                                                                                                                                                                                                                              | string\|number                                  |            |
 | onChange          | Callback invoked when form update, including Fields mount/unmount / value change / <br/> blur / validation status change / error status change.                                                                                                                                                                     | function (formState: object)                    |            |
-| onValueChange     | Callback invoked when form values update                                                                                                                                                                                                                                                                            | function (values: object, changedValue: object) |
+| onErrorChange     | Callback when the validation state of form updated. The first parameter: formState.errors, second parameter: name of the field that has changed and it's error message (available after v2.66)                                                       | function(values:object, changedError: object) |            |
+| onValueChange     | Callback invoked when form values update. The first parameter: formState.values, second parameter: name of the field and it's value                                                                                                                                                                                                                                                                            | function (values: object, changedValue: object) |
 | onReset           | Callback invoked after clicked on reset button or executed `formApi.reset()`                                                                                                                                                                                                                                        | function ()                                     |            |
 | onSubmit          | Callback invoked after clicked on submit button or executed `formApi.submit()`, <br/>and all validation pass.                                                                                                                                                                                                        | function (values: object, e: event)                       |            |
 | onSubmitFail      | Callback invoked after clicked on submit button or executed `formApi.submit()`,<br/> but validate failed.                                                                                                                                                                                                            | function (error: object, values: object, e: event)               |            |

+ 1 - 0
content/input/form/index.md

@@ -2066,6 +2066,7 @@ render(WithFieldDemo2);
 | labelWidth        | 统一配置label 宽度                                                                                                                                                                   | string\|number                                |            |
 | onChange          | form 更新时触发,包括表单控件挂载/卸载/值变更/blur/验证状态变更/错误提示变更, 入参为 formState                                                                               | function(formState:object)                    |            |
 | onValueChange     | form 的值被更新时触发,仅在表单控件值发生变化时触发。第一个入参为 formState.values,第二个入参为当前发生变化的 field                                                         | function(values:object, changedValue: object) |            |
+| onErrorChange     | form 的校验状态被更新时触发。第一个入参为 formState.errors,第二个入参为当前发生变化的 field 的名称与校验结果(v2.66后提供)                                                        | function(values:object, changedError: object) |            |
 | onReset           | 点击 reset 按钮或调用 `formApi.reset()`时的回调函数                                                                                                                          | function()                                    |            |
 | onSubmit          | 点击 submit 按钮或调用 `formApi.submitForm()`,数据验证成功后的回调函数                                                                                                      | function(values:object, e: event)                       |            |
 | onSubmitFail      | 点击 submit 按钮或调用 `formApi.submitForm()`,数据验证失败后的回调函数                                                                                                      | function(errors:object, values:object, e: event)        |            |

+ 7 - 2
packages/semi-foundation/form/foundation.ts

@@ -194,6 +194,7 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
                         } else {
                             this.data.errors = result;
                             this._adapter.notifyChange(this.data);
+
                             this.injectErrorToField(result);
                             this._adapter.forceUpdate();
                             this._autoScroll(100);
@@ -212,6 +213,7 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
                 this.data.errors = maybePromisedErrors;
                 this.injectErrorToField(maybePromisedErrors);
                 this._adapter.notifyChange(this.data);
+
                 this._adapter.forceUpdate();
                 this._autoScroll(100);
                 reject(maybePromisedErrors);
@@ -241,6 +243,7 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
             Promise.all(promiseSet).then(() => {
                 // After the centralized verification is completed, trigger notify and forceUpdate once.
                 this._adapter.notifyChange(this.data);
+
                 this._adapter.forceUpdate();
                 const errors = this.getError();
                 if (this._isValid(targetFields)) {
@@ -493,12 +496,14 @@ export default class FormFoundation extends BaseFoundation<BaseFormAdapter> {
         const notNotify = opts && opts.notNotify;
         const notUpdate = opts && opts.notUpdate;
         ObjectUtil.set(this.data.errors, field, error);
+
+        
         // The setError caused by centralized validation does not need to trigger notify, otherwise it will be called too frequently, as many times as there are fields
-        // 集中validate时,引起的setError不需要触发notify,否则会过于频繁调用,有多少个field就调用了多少次
         if (!notNotify) {
             this._adapter.notifyChange(this.data);
         }
-
+        this._adapter.notifyErrorChange(this.data.errors, { [field]: error });
+        
         if (!notUpdate) {
             this._adapter.forceUpdate(callback);
         }

+ 1 - 0
packages/semi-foundation/form/interface.ts

@@ -17,6 +17,7 @@ export interface BaseFormAdapter<P = Record<string, any>, S = Record<string, any
     forceUpdate: (callback?: () => void) => void;
     notifyChange: (formState: FormState) => void;
     notifyValueChange: (values: any, changedValues: any) => void;
+    notifyErrorChange: (errors: any, changedError: any) => void;
     notifyReset: () => void;
     getInitValues: () => Partial<Values>;
     getFormProps: (keys: undefined | string | Array<string>) => any;

+ 16 - 5
packages/semi-ui/form/_story/Validate/validateDemo.jsx

@@ -70,8 +70,10 @@ class ValidateFieldsDemo extends Component {
                     autoScrollToError
                     validateFields={this.syncValidate}
                     onReset={v => console.log('reset')}
-                    onChange={v => console.log(v)}
-                    onValueChange={v => console.log('onValueChange')}>
+                    // onChange={v => console.log(v)}
+                    onValueChange={(values, changedField) => console.log('onValueChange', values, changedField)}
+                    onErrorChange={(errors, changedField) => console.log('onErrorChange', errors, changedField)}    
+                >
                     <Form.InputGroup label="group" style={{ width: 600 }}>
                         <Input field="group.name" style={{ width: 280 }} />
                         <Input field="group.sort" style={{ width: 290 }} />
@@ -142,7 +144,10 @@ class CustomValidateDemo extends Component {
 
     render() {
         return (
-            <Form autoScrollToError>
+            <Form
+                autoScrollToError
+                onErrorChange={(errors, changedField) => console.log(errors, changedField)}
+            >
                 <Input field="name" validate={this.asyncValidate} trigger="blur" />
                 <Input field="familyName" validate={this.validateName} trigger="blur" name="familyName" />
                 <Input field="code" validate={this.asyncValidate} trigger={['change', 'mount']} />
@@ -203,7 +208,11 @@ class PartValidAndResetDemo extends Component {
     render() {
         let options = ['a', 'b', 'c', 'd', 'b.name'].map(item => ({ label: item, value: item }));
         return (
-            <Form getFormApi={this.getFormApi} autoScrollToError>
+            <Form
+                getFormApi={this.getFormApi}
+                autoScrollToError
+                onErrorChange={(errors, changedField) => console.log(errors, changedField)}
+            >
                 <Input field="a[1]" validate={this.validate} trigger="blur" />
                 <Input field="a[0]" validate={this.validate} trigger="blur" />
                 <Input field="ackk" validate={this.validate} trigger="blur" />
@@ -258,7 +267,9 @@ class RulesValidateDemo extends Component {
                     autoScrollToError
                     onReset={v => console.log('reset')}
                     onChange={v => console.log(v)}
-                    onValueChange={v => console.log('onValueChange')}>
+                    onValueChange={v => console.log('onValueChange')}
+                    onErrorChange={(errors, changedField) => console.log(errors, changedField)}
+                >
                     <Input field="panel[0].a" trigger="custom" rules={[{ required: true, message: '字段不能为空' }]} />
                     <Input field="panel[0].b" trigger="custom" rules={[{ required: true, message: '字段不能为空' }]} />
                     <Input field="panel[0].c" trigger="custom" rules={[{ required: true, message: '字段不能为空' }]} />

+ 6 - 1
packages/semi-ui/form/baseForm.tsx

@@ -13,7 +13,7 @@ import { cloneDeep } from '../_utils/index';
 import Slot from './slot';
 import Section from './section';
 import Label from './label';
-import ErrorMessage from './errorMessage';
+import ErrorMessage, { ReactFieldError } from './errorMessage';
 import FormInputGroup from './group';
 import { noop } from 'lodash';
 import '@douyinfe/semi-foundation/form/form.scss';
@@ -93,6 +93,7 @@ class Form<Values extends Record<string, any> = any> extends BaseComponent<BaseF
         onSubmit: noop,
         onReset: noop,
         onValueChange: noop,
+        onErrorChange: noop,
         layout: 'vertical',
         labelPosition: 'top',
         allowEmpty: false,
@@ -180,6 +181,9 @@ class Form<Values extends Record<string, any> = any> extends BaseComponent<BaseF
             notifyValueChange: (values: Values, changedValues: Partial<Values>) => {
                 this.props.onValueChange(values, changedValues);
             },
+            notifyErrorChange: (errors: Record<keyof Values, ReactFieldError>, changedError: Partial<Record<keyof Values, ReactFieldError>>) => {
+                this.props.onErrorChange(errors, changedError);
+            },
             notifyReset: () => {
                 this.props.onReset();
             },
@@ -269,6 +273,7 @@ class Form<Values extends Record<string, any> = any> extends BaseComponent<BaseF
             onChange,
             onSubmit,
             onSubmitFail,
+            onErrorChange,
             onValueChange,
             component,
             render,

+ 2 - 2
packages/semi-ui/form/interface.ts

@@ -62,7 +62,7 @@ export type CommonexcludeType = {
 
 export type RadioCheckboxExcludeProps = {
     defaultValue?: any;
-    chekced?: boolean;
+    checked?: boolean;
     defaultChecked?: boolean;
     field: string
 };
@@ -97,13 +97,13 @@ export interface FormFCChild<K extends Record<string, any> = any> {
 }
 
 
-
 export interface BaseFormProps <Values extends Record<string, any> = any> extends Omit<React.FormHTMLAttributes<HTMLFormElement>, 'children' | 'onChange' | 'onSubmit' | 'onReset'> {
     'aria-label'?: React.AriaAttributes['aria-label'];
     onSubmit?: (values: Values, e?: React.FormEvent<HTMLFormElement>) => void;
     onSubmitFail?: (errors: Record<keyof Values, FieldError>, values: Partial<Values>, e?: React.FormEvent<HTMLFormElement>) => void;
     onReset?: () => void;
     onValueChange?: (values: Values, changedValue: Partial<Values>) => void;
+    onErrorChange?: (errors: Record<keyof Values, FieldError>, changedError?: Partial<Record<keyof Values, FieldError>>) => void;
     onChange?: (formState: FormState<Values>) => void;
     allowEmpty?: boolean;
     validateFields?: (values: Values) => string | Partial<AllErrors<Values>>;