使用 formik、yup 和 axios 去抖动

Posted

技术标签:

【中文标题】使用 formik、yup 和 axios 去抖动【英文标题】:Debouncing with formik, yup and axios 【发布时间】:2018-12-23 11:05:37 【问题描述】:

尝试消除 api 调用以检查给定的电子邮件地址是否已被占用。

这是错误的逻辑。

const request = email => 
  return Axios.get(
    'http://localhost:4000/api/v1/users/precheck?user[email]=' + email,
  ).then(response => 
    return response.data.precheck == 'ok';
  );
;

const checkEmail = _.debounce(request, 1000);

export const Signup = withFormik(
  mapPropsToValues: () => ( email: '' ),
  validationSchema: object().shape(
    email: string()
      .email('Invalid email address')
      .required('Email is required!')
      .test('email', 'email already taken', email => 
        _.debounce(checkEmail(email));
      ),
  ),
 ...
)(InnerForm);

这会产生

createValidation.js:63 Uncaught (in promise) ValidationError name: "ValidationError", value: "aaa", path: "email", type: undefined, errors: Array(1), …
Promise.then (async)
validate @ createValidation.js:107
(anonymous) @ mixed.js:245
(anonymous) @ mixed.js:244
Promise.then (async)
_validate @ mixed.js:238
validate @ mixed.js:256
(anonymous) @ object.js:202
(anonymous) @ object.js:188
Promise.then (async)
_validate @ object.js:179
validate @ mixed.js:256
validateYupSchema @ formik.esm.js:547
(anonymous) @ formik.esm.js:205
Formik._this.runValidationSchema @ formik.esm.js:200
Formik._this.runValidations @ formik.esm.js:217
executeChange @ formik.esm.js:257
Formik._this.handleChange @ formik.esm.js:269
callCallback @ react-dom.development.js:100
invokeGuardedCallbackDev @ react-dom.development.js:138
invokeGuardedCallback @ react-dom.development.js:187
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:201
executeDispatch @ react-dom.development.js:461
executeDispatchesInOrder @ react-dom.development.js:483
executeDispatchesAndRelease @ react-dom.development.js:581
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:592
forEachAccumulated @ react-dom.development.js:560
runEventsInBatch @ react-dom.development.js:723
runExtractedEventsInBatch @ react-dom.development.js:732
handleTopLevel @ react-dom.development.js:4476
batchedUpdates$1 @ react-dom.development.js:16659
batchedUpdates @ react-dom.development.js:2131
dispatchEvent @ react-dom.development.js:4555
interactiveUpdates$1 @ react-dom.development.js:16714
interactiveUpdates @ react-dom.development.js:2150
dispatchInteractiveEvent @ react-dom.development.js:4532
formik.esm.js:529 Uncaught (in promise) TypeError: Cannot read property 'length' of undefined
    at yupToFormErrors (formik.esm.js:529)
    at formik.esm.js:208
yupToFormErrors @ formik.esm.js:529
(anonymous) @ formik.esm.js:208
Promise.then (async)
(anonymous) @ formik.esm.js:205
Formik._this.runValidationSchema @ formik.esm.js:200
Formik._this.runValidations @ formik.esm.js:217
executeChange @ formik.esm.js:257
Formik._this.handleChange @ formik.esm.js:269
callCallback @ react-dom.development.js:100
invokeGuardedCallbackDev @ react-dom.development.js:138
invokeGuardedCallback @ react-dom.development.js:187
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:201
executeDispatch @ react-dom.development.js:461
executeDispatchesInOrder @ react-dom.development.js:483
executeDispatchesAndRelease @ react-dom.development.js:581
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:592
forEachAccumulated @ react-dom.development.js:560
runEventsInBatch @ react-dom.development.js:723
runExtractedEventsInBatch @ react-dom.development.js:732
handleTopLevel @ react-dom.development.js:4476
batchedUpdates$1 @ react-dom.development.js:16659
batchedUpdates @ react-dom.development.js:2131
dispatchEvent @ react-dom.development.js:4555
interactiveUpdates$1 @ react-dom.development.js:16714
interactiveUpdates @ react-dom.development.js:2150
dispatchInteractiveEvent @ react-dom.development.js:4532

【问题讨论】:

我远非 Formik 专家,但您真的应该对验证函数本身进行去抖动吗?使用email => request(email) 进行验证,并反跳整个handleChange 函数不是更好吗? 我认为这可能会导致与不需要去抖动的电子邮件的其他验证同步问题。 好的。你有没有偶然发现this gist by Jared, the creator of Formik? 很好,谢谢你去看看! 【参考方案1】:

对于仍在寻找的人,这是我设法做到的:

export function asyncDebounce(func: any, wait: any): (...args: any[]) => Promise<unknown> 
  const debounced: DebouncedFunc<(resolve: any, reject: any, bindSelf: any, args: any) => Promise<void>> =
    debounce(async (resolve: any, reject: any, bindSelf: any, args: any) => 
      try 
        const result: any = await func.bind(bindSelf)(...args);
        resolve(result);
       catch (error) 
        reject(error);
      
    , wait);

  // This is the function that will be bound by the caller, so it must contain the `function` keyword.
  function returnFunc(...args: any[]) 
    return new Promise((resolve, reject) => 
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line no-invalid-this
      debounced(resolve, reject, this, args);
    );
  

  return returnFunc;

但是现在你必须使用 ref 和 raw apollo 查询:

  const apolloClient: ApolloClient<any> = useApolloClient();

  const emailAddressDebounce: React.MutableRefObject<(...args: any[]) => Promise<unknown>> =
  useRef(asyncDebounce(apolloClient.query, 350));
email: Yup.string()
        .email('Invalid email address')
        .required('Required')
        .test(
          'email-backend-validation',
          'Email already in use',
          async (email: string | undefined) => 
            if (!email) 
              return true;
            

            const result: EmailTakenQueryData = await emailAddressDebounce.current(
              query: emailAddressTakenQuery,
              variables:  email ,
            ) as unknown as EmailTakenQueryData;

            if (result?.data?.emailAddressTaken) 
              return !result?.data?.emailAddressTaken;
            

            return true;
          ),

这很有效:)

【讨论】:

以上是关于使用 formik、yup 和 axios 去抖动的主要内容,如果未能解决你的问题,请参考以下文章

使用 Formik 和 Yup 验证 2 个字段是不是相等

使用 Formik 和 Yup 和 React-select 进行验证

Formik 和 yup 验证不适用于所需

React JS:Yup & Formik 错误消息在提交时显示多次

Yup.mixed().test() 似乎破坏了 Formik 表单验证

Formik + Yup:如何在提交前立即验证表单?