alibaba-fusion / next

🦍 A configurable component library for web built on React.
https://fusion.design
MIT License
4.58k stars 584 forks source link

[Validate] field校验,在加入debounce后失效 #3343

Open cwtuan opened 3 years ago

cwtuan commented 3 years ago

Component

Field

Reproduction link

https://riddle.alibaba-inc.com/riddles/3ba2f6d5

import React from 'react@16';
import ReactDOM from 'react-dom@16';
import { Input, Button, Checkbox, Field } from '@alifd/next';
import _ from 'lodash';

const CheckboxGroup = Checkbox.Group;

const list = [
    {
        value: 'apple',
        label: 'apple'
    }, {
        value: 'pear',
        label: 'pear'
    }, {
        value: 'orange',
        label: 'orange'
    }
];

const sleep=(time) => {
    return new Promise(resolve => {
      setTimeout(resolve, time);
    });
  }

  const  userName=async (rule, value, callback) =>{
        console.log('userName',value)
        await sleep(1000);

        if (value === 'aaa') {
            callback('name existed')
        } else {
            callback()
        }
    }

const userNameDebounce=_.debounce(userName, 300);

class App extends React.Component {
    state = {
        checkboxStatus: true
    }
    field = new Field(this, {scrollToFirstError: -10});

    isChecked(rule, value, callback) {
        if (!value) {
            return callback('consent agreement not checked ');
        } else {
            return callback();
        }
    }

    render() {
        const init = this.field.init;

        return (<div className="demo">

            <Input defaultValue="" placeholder="try aaa" {...init('username', {
                rules: [{
                    validator: userNameDebounce,
                    trigger: ['onBlur', 'onChange']
                }]
            })} />
            {this.field.getState('username') === 'loading' ? 'validating...' : ''}
            {this.field.getError('username') ?
                <span style={{color: 'red'}}>{this.field.getError('username').join(',')}</span> : ''}

            <br/>
            <br/>

            <Button type="primary" onClick={() => {
                this.field.validate((errors, values) => {
                    console.log(errors, values);
                });
            }}>validate</Button>
            <Button onClick={() => {
                this.field.reset();
            }}>reset</Button>

            <Button onClick={() => {
                if (this.state.checkboxStatus) {
                    this.setState({checkboxStatus: false});
                    this.field.remove('checkboxgroup');
                } else {
                    this.setState({checkboxStatus: true});
                }

            }}>{this.state.checkboxStatus ? 'delete' : 'restore'}</Button>
        </div>);
    }
}

ReactDOM.render(<App/>, mountNode);

Steps to reproduce

  1. 第一次输入"aaa",展示错误 => 符合预期 image

  2. 删除一个"a",没错误,没显示错误 => 符合预期 image

  3. 再输入一个"a",应该错误,却没显示错误 => 不符合预期 image

把debounce去除就正常了。

bindoon commented 3 years ago

初步评估应该是debance导致没执行到callback导致的,后续看看是否可以优化!

cwtuan commented 3 years ago

我在cb之前打印了一下,有执行了cb,但是没展示错误 image

JohnGao818 commented 2 years ago

遇到了同样的问题,请问你想到方案解决了吗?

zuoqi705 commented 2 years ago

你好,我是赵佐骑我已收到您的邮件,并会尽快回复。

bindoon commented 2 years ago

我在cb之前打印了一下,有执行了cb,但是没展示错误 image

因为多次校验,validate 只认最后一次的执行结果。否则会出现第二次返回慢而展示第一次错误的情况。

bindoon commented 2 years ago

debance 的问题我加紧下,本周给出方案

bindoon commented 2 years ago

排查出来了,因为你用了 async 异步函数,但是返回的又不是 Promise。

不要用 async ,直接 userName = function() { return new Promise} 来解决吧

cwtuan commented 2 years ago

async默认应该就是返回Promise了。 另外,我也改成return new Promise的方式,还是不行 image

bindoon commented 2 years ago

你都用用 Promise了,可以直接 resolve(), reject('error') ;如下用法 setTimeout 模拟异步请求

const userName = (rule, value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value === 'aaa') {
        console.log('userName fail', value)
        reject('name existed');
      } else {
        console.log('userName success', value)
        resolve();
      }
    }, 100)
  })
}
cwtuan commented 2 years ago

image

@bindoon 你提供的解法,我fork在这里,出现校验状态错误问题

cwtuan commented 2 years ago

any update?

cwtuan commented 1 year ago

any update?

YSMJ1994 commented 7 months ago

自定义 validation 经过 debounce 只能以 callback 形式执行,因为 debounce 后的函数不会稳定返回执行结果,validation返回的promise对象不能稳定返回给校验器,所以无法正确校验。 然后 callback 形式的 validation 不要返回 promise 对象,否则会被认定为 promise 模式,从而导致上面的异常。

如下代码可解:

const userName = (rule, value, callback) => {
  setTimeout(() => {
      if (value === 'aaa') {
        console.log('userName fail', value)
        callback('name existed');
      } else {
        console.log('userName success', value)
        callback();
      }
    }, 100)
}

若需要在业务逻辑里执行 async,则可以再封装一层解决:

const asyncUserName = async (rule, value) => {
   await sleep(1000);
   return value ? undefined : 'error msg';
}
const userName = (rule, value, callback) => {
   asyncUserName(rule,value).then(callback);
}

修复后示例 fock