如何消除异步 formik/是的验证,它会在用户停止输入数据时进行验证?

Ana*_*oly 9 reactjs yup formik

我想异步验证用户输入。例如,检查电子邮件是否已存在,并在用户键入时执行验证。为了减少 API 调用,我想使用 lodash 或自定义去抖函数来去抖 API 调用,并在用户停止输入时执行验证。

到目前为止,这就是我的表格。问题是它没有按预期工作。看来被谴责的函数返回了上一次调用的值,我不明白问题出在哪里。

您可以在这里看到一个实例: https: //codesandbox.io/s/still-wave-qwww6

import { isEmailExists } from "./api";

const debouncedApi = _.debounce(isEmailExists, 300, {
  trailing: true
});

export default function App() {
  const validationSchema = yup.object({
    email: yup
      .string()
      .required()
      .email()
      .test("unique_email", "Email must be unique", async (email, values) => {
        const response = await debouncedApi(email);
        console.log(response);
        return response;
      })
  });

  const formik = useFormik({
    initialValues: {
      email: ""
    },
    validateOnMount: true,
    validationSchema: validationSchema,
    onSubmit: async (values, actions) => {}
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label>
        Email:
        <input
          type="text"
          name="email"
          onChange={formik.handleChange}
          onBlur={formik.handleBlur}
          value={formik.values.email}
        />
        <div className="error-message">{formik.errors.email}</div>
      </label>
    </form>
  );
}
Run Code Online (Sandbox Code Playgroud)

我使用以下函数模拟 API 调用:

export const isEmailExists = async email => {
    return new Promise(resolve => {
        console.log('api call', email);
        setTimeout(() => {
            if (email !== 'test@gmail.com') {
                return resolve(true);
            } else {
                return resolve(false);
            }
        }, 200);
    })
}
Run Code Online (Sandbox Code Playgroud)

更新:尝试编写我自己的去抖函数实现。这样,最后一个 Promise 的解析将被保留到超时,只有那时函数才会被调用,Promise 才会被解析。

const debounce = func => {
    let timeout;
    let previouseResolve;
    return function(query) {
         return new Promise(async resolve => {

            //invoke resolve from previous call and keep current resolve
            if (previouseResolve) {
                const response = await func.apply(null, [query]);
                previouseResolve(response);
            }
            previouseResolve = resolve;

            //extending timeout
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            timeout = setTimeout(async () => {
                const response = await func.apply(null, [query]);
                console.log('timeout expired', response);
                previouseResolve(response);
                timeout = null;
            }, 200);
        })
    }
}

const debouncedApi = debounce(isEmailExists);

 const validationSchema = yup.object({
        email: yup
            .string()
            .required()
            .email()
            .test('unique_email', 'Email must be unique', async (email, values) => {
                const response = await debouncedApi(email);
                console.log('test response', response);
                return response;
            })
    });
Run Code Online (Sandbox Code Playgroud)

不幸的是,它也不起作用。看起来是的,当下一次调用发生时,会中止未解析的函数调用。当我打字快时,它不起作用,当我打字慢时,它起作用。您可以在此处查看更新的示例:https ://codesandbox.io/s/suspicious-chaum-0psyp

sea*_*ong 6

看来被谴责的函数返回了上一次调用的值

这就是 lodash debounce 的工作原理:

对去抖函数的后续调用将返回最后一次 func 调用的结果。

请参阅: https: //lodash.com/docs/4.17.15#debounce

您可以设置validateOnChangefalse然后formik.validateForm手动调用作为副作用:

import debounce from 'lodash/debounce';
import { isEmailExists } from "./api";

const validationSchema = yup.object({
  email: yup
    .string()
    .required()
    .email()
    .test("unique_email", "Email must be unique", async (email, values) => {
      const response = await isEmailExists(email);
      console.log(response);
      return response;
    })
});

export default function App() {
  const formik = useFormik({
    initialValues: {
      email: ""
    },
    validateOnMount: true,
    validationSchema: validationSchema,
    validateOnChange: false, // <--
    onSubmit: async (values, actions) => {}
  });

  const debouncedValidate = useMemo(
    () => debounce(formik.validateForm, 500),
    [formik.validateForm],
  );

  useEffect(
    () => {
      console.log('calling deboucedValidate');
      debouncedValidate(formik.values);
    },
    [formik.values, debouncedValidate],
  );

  return (
    ...
  );
}
Run Code Online (Sandbox Code Playgroud)

这样,整个验证将被反跳,而不仅仅是远程调用。

如果没有依赖关系,最好将架构放在组件之外,但在每次渲染中执行此操作通常很慢。