'Yup / Formik async validation with debounce

How can debounce be applied to the async validation below (code from Yup's github) ?

let asyncJimmySchema = string().test(
  'is-jimmy',
  '${path} is not Jimmy',
  async (value) => (await fetch('/is-jimmy/' + value)).responseText === 'true',
});


Solution 1:[1]

You can implement it by your self by using lodash.debounce:

import { debounce } from "lodash";

// .....

const ASYNC_VALIDATION_TIMEOUT_IN_MS = 1000;

const validationFunction = async (value, resolve) => {
  try {
    const response = await fetch('/is-jimmy/' + value);
    resolve(response.responseText === 'true');
  } catch (error) {
    resolve(false);
  }
};

const validationDebounced = debounce(validationFunction, ASYNC_VALIDATION_TIMEOUT_IN_MS);

Then in the validation scheme:

let asyncJimmySchema = string().test(
  'is-jimmy',
  '${path} is not Jimmy',
  value => new Promise(resolve => validationDebounced(value, resolve)),
});

Solution 2:[2]

I think this should work. It's copying the solution from just-debounce-it but returning a Promise right away, which is what Yup expects.

const asyncDebouncer = (fn, wait, callFirst) => {
  var timeout;
  return function() {
    return new Promise(async (resolve) => {
      if (!wait) {
        const result = await fn.apply(this, arguments);
        resolve(result);
      }

      var context = this;
      var args = arguments;
      var callNow = callFirst && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(async function() {
        timeout = null;
        if (!callNow) {
          const result = await fn.apply(context, args);
          resolve(result);
        }
      }, wait);

      if (callNow) {
        const result = await fn.apply(this, arguments);
        resolve(result);
      }
    });
  };
};

let asyncJimmySchema = string().test(
  'is-jimmy',
  '${path} is not Jimmy',
  asyncDebouncer((value) => (await fetch('/is-jimmy/' + value)).responseText === 'true', 400),
});

Solution 3:[3]

Make sure that you are not creating a new instance of the debouncer each time:

import { debounce } from 'lodash'

const isJimmy = async (value,resolve) => {
    const response = await fetch('/is-jimmy/' + value))
    resolve(response.responseText === 'true')
}

const debounceInstance = (func,delay=1000) => {
    const funcDebounced = debounce(func,delay)
    return (newValue) => 
        new Promise(resolve => 
            funcDebounced(newValue,resolve))
}

//BAD - creates a new instance on each call
const asyncJimmySchema = string()
  .test(val=> debounceInstance(isJimmy)(val)});

//GOOD - same instance for each call
const asyncJimmySchema = string()
  .test(debounceInstance(isJimmy))}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Alex
Solution 2 christo8989
Solution 3