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
Liked the post? Click the beard up to 50 times to show it
Share
Sharing this post on Twitter & elsewhere is a great way to help me out
Support
Was this post valuable to you? Make a donation to show it
Make a Donation
Kofi logo

Newer Post: Conditional React Hooks
Tags
ReactReact Hooks

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.

Introduction to State Machines and XState Logo
Introduction to State Machines and XState

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.