Using debounce onChange handlers with Typescript
Published:
Heads up! This content is more than six months old. Take some time to verify everything still works as expected.
For this example, I've mixed together lodash's debounce with React's hooks. The same should roughly apply in other frameworks.
For the important bits, we're looking at a few of specifics:
First, note that debounce
accepts
generic type variables.
This provides us with a place to define the return function's type
a typical place for typings to be lost working with debounce
. Try this instead:
debounce<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>(event);
The incoming event is also typically lost, so we can correct that again on the event:
(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {};
The debounce
function above would be minimally functional passed directly
to something like onChange
. It still suffers two issues we can correct:
There's a minor performance gain to be snagged by caching the debounced function.
In React, we can store the results of a function that runs (even if the result
is a function), but through useMemo
.
const onSearchChange = useMemo( debounce(() => {}, 250), [ // Dependencies ] );
Then there's the potentially nasty side effect of debounce
to deal with, timing.
Depending on your implementation and side effects, the debounced callback function
might try to fire after your component unloads resulting in errors. So, we fire a
cancel event using the useEffect
's
return clean up function.
useEffect(() => { // Note that this is a clean up effect, so its logic must be in a returned function. return () => { onSearchChange?.cancel(); }; }, [onSearchChange]);
For a full working example, have look over this sample component.
import React, { useState, useEffect, ChangeEvent, useMemo, ChangeEventHandler, } from "react"; import { TextField } from "@material-ui/core"; import { debounce } from "lodash"; const SampleComponent: React.FunctionComponent = () => { const [searchQuery, setSearchQuery] = useState<string | undefined>(); // Create a cached debounce function for change handling const onSearchChange = useMemo( () => debounce<ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>>( (event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { setSearchQuery(event.target.value); }, 250 ), [setSearchQuery] ); // Stop the invocation of the debounced function after unmounting useEffect(() => { // Note that this is a clean up effect, so its logic must be in a returned function. return () => { onSearchChange?.cancel(); }; }, [onSearchChange]); return ( <TextField autoFocus placeholder="Page" type={"search"} onChange={onSearchChange} value={searchQuery} /> ); }; export default SampleComponent;