I want to validate user input asynchronously. For example, to check if email already exists, and perform validation while the user typing. To decrease API calls I'd like to debounce API calls with lodash or custom debounce function and to perform validation when the user stops typing.
So far this is my form right now. The issue is that it doesn't work as intended. It looks that denounced function returns a value from the previous call, and I can't understand where is the problem.
You can see a live example here: 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>
);
}
I emulate API call by using following function:
export const isEmailExists = async email => {
return new Promise(resolve => {
console.log('api call', email);
setTimeout(() => {
if (email !== '[email protected]') {
return resolve(true);
} else {
return resolve(false);
}
}, 200);
})
}
UPDATE: Tried to write my own implementation of debounce function. In such a way, that last Promise' resolve will be kept till timeout expired, and only then function will be invoked and Promise will be resolved.
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;
})
});
Unfortunately, it doesn't work either. It looks like yup abort unresolved function calls when the next call happens. When I type fast it doesn't work, when I type slow it works. You can see updated example here: https://codesandbox.io/s/suspicious-chaum-0psyp
debouncedValidate
inuseEffect
? – Proterozoic