Let’s start with what debouncing is. Debouncing is a way of calling a function any number of times but only allowing it to execute after a set amount of time has passed since the last time it was called.
I recently ran into an issue using material-table where filtering was taking an unacceptably long time. The reason it was running so slowly is that each time a letter was pressed the filtering query ran on a large dataset. What I really want is for the user to finish typing and then filter the results one time.
Let’s step through how we can accomplish this. A simple debounce function can be written as
function debounce( callback, delay ) { let timeout; return function() { clearTimeout( timeout ); timeout = setTimeout( callback, delay ); } }
To understand how exactly this works you need to understand closures. In a nutshell, the timeout variable is stored between calls to debounce. This allows us to clear any previous timeout, if one exists, and create a new timeout callback each time the function is called. Again, this gives us the effect that the callback function is only called after the delay amount of time has passes since the last time the debounce function is called.
In order to implement this with react I used the npm package use-debounce.
To give you a quick example of what we are creating, here is an example of the product in action
Here is the code that mad that happen
const [text, setText] = useState(''); const [characters, setCharacters] = useState([]) const handleChange = (event) => { setText(event.target.value); debounced.callback(event.target.value) }; const debounced = useDebouncedCallback( // function (value) => { fetch(`https://rickandmortyapi.com/api/character/?name=${text}`) .then(r => r.json()) .then((resp)=> { setCharacters(resp.results)} ) .catch((error)=> console.log(`${JSON.stringify(error)}`)) }, // delay in ms 1000 ); return ( <div className={classes.App}> <TextField value={text} label={`Character Search`} onChange={handleChange} variant="outlined" /> { characters && characters.map((char)=> <Grid key={char.id} container direction="row" className={classes.character}> <Grid item> <Avatar src={char.image} alt={char.name} className={classes.avatar}/> </Grid> <Grid item> {char.name} </Grid> </Grid> ) } </div> )
Let’s step through how this works. On line 21, we added ‘handleChange’ to get called every time the user edits the text on the TextField.
<TextField value={text} label={`Character Search`} onChange={handleChange} variant="outlined" />
Our handleChange event on line 4 does 2 things.
const handleChange = (event) => { setText(event.target.value); debounced.callback(event.target.value) };
First, it updates the textbox that we are looking at to reflect the the changes the user made to the TextField. Second, it calls our debounce function with the text from the TextField.
If the amount of time has passed that is defined on line 21 (1000), in milliseconds, since the last time debounced has been called, then the function defined on line 11 is called.
(value) => { fetch(`https://rickandmortyapi.com/api/character/?name=${text}`) .then(r => r.json()) .then((resp)=> { setCharacters(resp.results)} ) .catch((error)=> console.log(`${JSON.stringify(error)}`)) }
This function simply calls the rickandmortyapi and gets all characters that contain the letters typed in the TextField. The returned characters are then added to the characters array and the results are rendered to the screen via the jsx
{ characters && characters.map((char)=> <Grid key={char.id} container direction="row" className={classes.character}> <Grid item> <Avatar src={char.image} alt={char.name} className={classes.avatar}/> </Grid> <Grid item> {char.name} </Grid> </Grid> ) }
This just checks if there are characters and if there are, it shows a pictured followed by the character name. Below is a screenshot of the results
The source for a working copy with the example is available at https://bitbucket.org/AndySattler/react-debounce-example/src/master/