25
loading...
This website collects cookies to deliver better user experience
Debounce function forces a function to wait a certain amount of time before running again. The function is built to limit the number of times a function is called.
ControlledForm
and rename to DebounceForm
, and import this new component to use inside the App
.function App() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-lg-6 col-md-6">
<DebounceForm />
</div>
<div className="col-lg-6 col-md-6">
<FormControlled />
</div>
</div>
</div>
);
}
Debounce function is a higher-order function
Higher-Order function is a function that receives a function as an argument or returns the function as output.
debounce
, this function will reduce the number of times that we change the form state and the number of renders of the component. Below, we can see my implementation:export function debounce(fn, wait, immediate) {
let timeout;
return (...args) => {
const context = this;
const later = () => {
timeout = null;
if (!immediate) fn.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
fn.apply(context, args);
}
};
}
const callbackFunction = () => {}; // it will be executed into returned function
const time = 3000; // it's the delay time
const returnedFunction = debounce(callbackFunction, time);
returnedFunction(); // callbackFunction know my arguments
onChange
event from input
, and add 500 milliseconds. This way the form state will only change after 500 milliseconds when the user stops writing.import React, { useState, useRef, useEffect, useCallback } from "react";
import { debounce } from "../Debounce";
function Input({ error, label, onChange, ...rest }) {
const [touched, setTouched] = useState(false);
const inputRef = useRef(null);
const debounceInput = useCallback(debounce(onChange, 500), [debounce]);
const blurInput = useCallback(() => setTouched(true), [setTouched]);
useEffect(() => {
inputRef.current.addEventListener("input", debounceInput);
inputRef.current.addEventListener("blur", blurInput);
return () => {
inputRef.current.removeEventListener("input", debounceInput);
inputRef.current.removeEventListener("blur", blurInput);
};
}, [blurInput, debounceInput, inputRef]);
return (
<>
<label htmlFor={rest.name}>{label}</label>
<input className="form-control" {...rest} ref={inputRef} />
<span className="text-danger">{touched && error}</span>
</>
);
}
export default Input;
useCallback
. UseCallback is used when you want to memorize a function, this hook receives a function as an argument and memorizes it, and this hook will return the same function while the dependencies don't change. When some dependency is changed a new function is returned. But why do we need to do this? The functions inside a component will change every time that the component is rendered, so when I use useCallback
I know that the function returned is the same, unless some dependency is changed.A common mistake is to think functions shouldn’t be dependencies.
useEffect
we should pass this function as a dependency, and we know that the function will change in every component render, for this reason, we use useCallback
, if we don't, our component will be rendered unnecessary.useState
to save blur event state, and useRef
to create a reference to use in the input element. After that we use useCallback
with debounce
function and setTouched
.useEffect
receives blurInput
, debounceInput
, inputRef
as dependencies inside of the function that we use with useEffect. We use the input reference to register the functions to deal with input and blur events, after that, we just return a function that should remove the event listener functions.useValidation
is a hook that returns an object with errors and a property to show us if the form values are valid or not.import { useState, useEffect, useCallback } from "react";
import { ValidationError } from "yup";
function useValidation(values, schema) {
const [errors, setErrors] = useState({});
const [isValid, setIsValid] = useState(false);
const validate = useCallback(async () => {
try {
await schema.validate(values, { abortEarly: false });
setErrors({});
setIsValid(true);
} catch (e) {
if (e instanceof ValidationError) {
const errors = {};
e.inner.forEach((key) => {
errors[key.path] = key.message;
});
setErrors(errors);
setIsValid(false);
}
}
}, [schema, values]);
useEffect(() => {
validate();
}, [validate]);
return { errors, isValid };
}
export default useValidation;
useEffect
to keep the errors object and isValid property, by default isValid should be false, because when we start our form we don't have any values.validate
, this function should receive the form values and pass this value to object validation. If the form state has a valid value, we set an empty object in the errors state and true in isValid
property, but if it has any error, we need to know if is an error of validation (ValidationError instance), before setting them in the errors state and false in isValid
.useCallback
with validate
function and pass this function as a useEffect dependency.DebounceForm
component:useValidation
, now we want to know if the form is valid, so we just need to take this property.const { errors, isValid } = useValidation(form, FormValidations);
isValid
in the submit button.<div className="form-group">
<button
type="button"
className="btn btn-
primary"
disabled={!isValid}
>
Submit
</button>
</div>