Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Using Lodash debounce with React and TypeScript

December 22, 2021

Lodash is a package that contains lots of great utility functions. For example, Lodash’s debounce function delays invoking a function passed into it. It can help performance in some situations.

In this post, we will use debounce to search for a Star Wars character when the user stops typing.

Lodash debounce with React and TypeScript

An existing app

In our existing app, when the user enters criteria in the input element, a request is made to the Star Wars API to search for matching characters.

export default function App() {
  const [characters, setCharacters] = React.useState<string[]>([]);

  async function search(criteria: string) {
    const response = await fetch(
    const body = await response.json();
    return Character) =>;

  async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // 🐌 causes too many requests 😞
    setCharacters(await search(;

  return (
    <div className="App">
        placeholder="Enter your search"
        { => (
          <li key={character}>{character}</li>

The problem with the app is that the search request is made every time the user makes a keystroke in the input element. Ideally, we want the search request to be made only when the user has stopped typing. We can use the debounce function from Lodash to do this.

Installing Lodash

npm install lodash

Install the whole of Lodash rather than just the function (i.e. npm install lodash.debounce) because the app’s bundler (e.g. Webpack) will tree shake everything in Lodash that isn’t used in the app.

Lodash doesn’t contain TypeScript types in the core package, so we install these separately:

npm install @types/lodash

Importing debounce from Lodash

debounce is a named export in the lodash package, so we import it as follows:

import { debounce } from "lodash"

Using debounce - first attempt

We use debounce to invoke the call to search after 300 milliseconds:

async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  debounce(async () => {
    // 😕 debounced function never called
    setCharacters(await search(;
  }, 300);

So, in theory, the search should be invoked 300 milliseconds after the user stops typing.

This implementation doesn’t work though - the search function is never called. 😕

It doesn’t work because the debounced function is lost when the user types the next character, and handleChange is called again.

Using debounce - second attempt

We can lift the debounced function outside of the handleChange to resolve the problem:

const debouncedSearch = debounce(async (criteria) => {
  setCharacters(await search(criteria));
}, 300);

async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {

This works nicely, but there is a more performant approach.

Using debounce - more performant approach

At the moment, a new version of debouncedSearch is created on every render. We can use Reacts useRef to store the debounced function across renders:

const debouncedSearch = React.useRef(
  debounce(async (criteria) => {
    setCharacters(await search(criteria));
  }, 300)

Nice! 😊

Cleaning up

When the component unmounts, the search request could be still in progress. In this case, an error will occur when the characters state is set.

We can use the useEffect hook to cancel the debounced function to resolve this problem. The debounce handle has a handy cancel method that we can use to do this:

React.useEffect(() => {
  return () => {
}, [debouncedSearch]);

The code from this post is available in Codesandbox in the link below

🏃 Play with the code

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Using TypeScript with React
Find out more

Want more content like this?

Subscribe to receive notifications on new blog posts and courses

© Carl Rippon
Privacy Policy