March 23, 2021
0 strokes bestowed

Debounce and Throttle Callbacks with React Hooks

edit

This one will be short and to the point. I recently ran into a problem while trying to create a debounced callback with React hooks. If you're using the react-hooks ESLint package with the recommended settings, then it will warn you that you can't do the following:

import React from 'react'
import { debounce } from 'lodash'

function Search({ onSearch }) {
  const [value, setValue] = React.useState('')

  // This use of `useCallback` has a problem
  const debouncedSearch = React.useCallback(
    debounce(val => {
      onSearch(val)
    }, 750),
    [onSearch]
  )

  const handleChange = React.useCallback(
    e => {
      setValue(e.target.value)
      debouncedSearch(e.target.value)
    },
    [debouncedSearch]
  )

  return <input type="text" value={value} onChange={handleChange} />
}

This looks fine, but here's the warning:

Error: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead react-hooks/exhaustive-deps

useCallback expects an inline function. Handing it the returned function from a debounce or throttle doesn't cut the mustard. Why? The ESLint rule can't figure out what the exhaustive dependencies should be. How do we fix this?

It might seem odd, but useMemo comes to the rescue.

function Search({ onSearch }) {
  const [value, setValue] = React.useState('')

  // This is the solution
  const debouncedSearch = React.useMemo(
    () =>
      debounce(val => {
        onSearch(val)
      }, 750),
    [onSearch]
  )

  const handleChange = React.useCallback(
    e => {
      setValue(e.target.value)
      debouncedSearch(e.target.value)
    },
    [debouncedSearch]
  )

  return <input type="text" value={value} onChange={handleChange} />
}

Think about it. useMemo is used to store a memoized value that should only be recalculated when the dependencies change. So what if that memoized value is a function?

It wasn't obvious to me to do this at first, but it makes sense. We want the returned function, and we want it to have the correct exhaustive dependencies, and update whenever they change. useMemo does exactly that.

You can use this same technique with throttle, too. Really any higher ordered function that returns a function. Sky is the limit.

Lastly, credit where it's due. I came across this idea from this GitHub comment. Maybe give it a thumbs up if you found this helpful.


Finished reading?

Here are a few options for what to do next.

Like
+0
Liked the post? Click the beard a few times to show how much.
Share
Support
Kofi logo
Buy me a Kofi
Newer Post: Conditional React Hooks
Tags
ReactHooks

Let's talk some more about JavaScript, React, and software engineering.

I write a newsletter to share my thoughts and the projects I'm working on. I would love for you to join the conversation. You can unsubscribe at any time.

Data Structures and Algorithms Logo
Data Structures and Algorithms

Check out my courses!

Liked the post? You might like my video courses, too. Click the button to view this course or go to Courses for more information.
View on egghead.io
Kyle Shevlin's face, which is mostly a beard with eyes
Kyle Shevlin is a software engineer who specializes in JavaScript, React and front end web development.