Browse Source

fix: Fix race async validator when using field props.validate (#1375, 1376)

* fix: import description error in postTemplate
* fix: docsite tabindex spell warning
* fix: form async validate race when using fieldProps.validate, #1375, #1376
pointhalo 2 years ago
parent
commit
6d207f2882

+ 71 - 1
packages/semi-ui/form/_story/Validate/validateDemo.jsx

@@ -418,4 +418,74 @@ class RulesExample extends React.Component {
         );
     }
 }
-export { ValidateFieldsDemo, CustomValidateDemo, PartValidAndResetDemo, RulesValidateDemo, SetBugDemo, UnmountedLeafDemo, RulesExample };
+
+
+
+class RaceAsyncDemo extends React.Component {
+    constructor() {
+        super();
+        this.asyncValidate = this.asyncValidate.bind(this);
+    }
+
+    asyncValidate(val, values) {
+        const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+        let time = 1000; 
+        if (val === 'semi') {
+            time = 4000;
+        } 
+        return sleep(time).then(() => {
+            if (!val) {
+                return 'can\'t be empty';
+            } else if (val === 'semi') {
+                return 'sleep 4000';
+            } else if (val === 'sem') {
+                return 'sleep 1000';
+            } else {
+                return '';
+            }
+        });
+    }
+
+    render() {
+        return (
+            <Form>
+                <Form.Input
+                    field='name'
+                    label='props.rules ract async validate'
+                    // validate={this.asyncValidate}
+                    trigger='blur'
+                    rules={[
+                        {
+                            type: 'string',
+                            asyncValidator: (rule, value) => {
+                                const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+                                let time = 1000; 
+                                if (value === 'semi') {
+                                    time = 4000;
+                                }
+                                return sleep(time).then(() => {
+                                    if (value === 'semi') {
+                                        throw Error('sleep 4000');
+                                    } else if (value === 'sem') {
+                                        throw Error('sleep 1000');
+                                    } else {
+                                        return;
+                                    }
+                                });
+                            }
+                        }
+                    ]}
+                />
+                <Form.Input
+                    field='nick'
+                    label='props.validate ract async validate'
+                    validate={this.asyncValidate}
+                    trigger='blur'
+                />
+                <Button htmlType="reset">reset</Button>
+            </Form>
+        );
+    }
+}
+
+export { ValidateFieldsDemo, CustomValidateDemo, PartValidAndResetDemo, RulesValidateDemo, SetBugDemo, UnmountedLeafDemo, RulesExample, RaceAsyncDemo };

+ 6 - 0
packages/semi-ui/form/_story/form.stories.jsx

@@ -62,6 +62,7 @@ import {
   SetBugDemo,
   UnmountedLeafDemo,
   RulesExample,
+  RaceAsyncDemo,
 } from './Validate/validateDemo';
 
 // field props
@@ -248,6 +249,11 @@ ValidateUseRules.story = {
   name: 'Validate-use rules',
 };
 
+RaceAsyncDemo.story = {
+  name: 'Validate - race async'
+}
+export const RaceAsync = () => <RaceAsyncDemo />;
+
 export const HooksUseFormApi = () => <UseFormApiDemo />;
 
 HooksUseFormApi.story = {

+ 13 - 2
packages/semi-ui/form/hoc/withField.tsx

@@ -234,8 +234,9 @@ function withField<
         };
 
         // execute custom validate function
-        const _validate = (val: any, values: any, callOpts: CallOpts) =>
-            new Promise(resolve => {
+        const _validate = (val: any, values: any, callOpts: CallOpts) => {
+
+            const rootPromise = new Promise(resolve => {
                 let maybePromisedErrors;
                 // let errorThrowSync;
                 try {
@@ -249,6 +250,11 @@ function withField<
                     updateError(undefined, callOpts);
                 } else if (isPromise(maybePromisedErrors)) {
                     maybePromisedErrors.then((result: any) => {
+                        // If the async validate is outdated (a newer validate occurs), the result should be discarded
+                        if (validatePromise.current !== rootPromise) {
+                            return;
+                        }
+
                         if (isValid(result)) {
                             // validate success,no need to do anything with result
                             updateError(undefined, callOpts);
@@ -269,6 +275,11 @@ function withField<
                     }
                 }
             });
+            
+            validatePromise.current = rootPromise;
+
+            return rootPromise;
+        };
 
         const fieldValidate = (val: any, callOpts?: CallOpts) => {
             let finalVal = val;

+ 1 - 1
src/sitePages/newHome/components/operateButton/operateButton.jsx

@@ -15,7 +15,7 @@ function OperateButton() {
     };
     return (<div className={styles.group2835}>
         <a href={`/${getLocale()}/start/getting-started`}>
-            <Button tabindex={-1} onClick={goStart} size="large" theme="solid" className={styles.extraLarge}>{_t("start_using", { }, "开始使用")}</Button>
+            <Button tabIndex={-1} onClick={goStart} size="large" theme="solid" className={styles.extraLarge}>{_t("start_using", { }, "开始使用")}</Button>
         </a>
         <Button
             onClick={goGithub} 

+ 2 - 2
src/templates/postTemplate.js

@@ -4,7 +4,7 @@ import { graphql, Link } from 'gatsby';
 import Blocks from '@douyinfe/semi-site-markdown-blocks';
 import '@douyinfe/semi-site-markdown-blocks/dist/index.css';
 import SearchAllInOne from '../components/SearchAllInOne';
-import { Icon, Row, Col, Tag, Tooltip, Popover, Checkbox, Button, Radio, Skeleton, Toast, Table, CheckboxGroup, Description, Dropdown, Form, Typography, Empty } from '@douyinfe/semi-ui';
+import { Icon, Row, Col, Tag, Tooltip, Popover, Checkbox, Button, Radio, Skeleton, Toast, Table, CheckboxGroup, Descriptions, Dropdown, Form, Typography, Empty } from '@douyinfe/semi-ui';
 import { IllustrationNoAccess, IllustrationNoAccessDark } from '@douyinfe/semi-illustrations';
 import NotificationCard from '../../packages/semi-ui/notification/notice';
 import ToastCard from '../../packages/semi-ui/toast/toast';
@@ -99,7 +99,7 @@ const SemiComponents = {
     ImageBox,
     // content guideline demo 
     CheckboxGroup: CheckboxGroup,
-    Description: Description,
+    Descriptions: Descriptions,
     NotificationCard,
     ToastCard,
     Dropdown,