Telerik blogs

In this article, we will look at how to create a custom debounce hook with React.

React is a JavaScript library used for creating interactive web frontend applications. It is one of the most popular libraries because of its easy-to-use API.

Most recent versions of React provide the Hooks API, which lets us create frontend JavaScript apps with function components. Hooks provides the logic for React function components.

One common feature that is implemented in frontend web apps is adding debounce for inputs. Debouncing lets us delay actions after an input’s value is changed. In this article, we will look at how to create a custom debounce hook with React.

Custom Hooks

We can create our own custom React hooks based on the basic hooks provided by React.

To do this, we create a function that calls the hooks and returns the values we want. Then we can get the returned values from our custom hook and do what we want with them in our components or other hooks.

For instance, we can make our own hook to get data and return it.

To do this, we write:

import { useCallback, useEffect, useState } from "react";

const useFetch = (url) => {
  const [data, setData] = useState();
  const [loading, setLoading] = useState();

  const getData = useCallback(async () => {
    const res = await fetch(url);
    const json = await res.json();
  }, [url]);

  useEffect(() => {
  }, [getData]);

  return { data, loading };

to define the useFetch hook.

A custom hook is just a pure function that uses other hooks. And a pure function is a function that takes some inputs and returns some outputs without committing any side effects.

In our useFetch hook, we define the data and loading states with the useState hook.

And then we call the useCallback hook with a function to make a request to the url with fetch.

We then call json to get the JSON response body and call setData to set the data state to the json response object.

Also, we call setLoading to set the loading state to true when the request is going to start and set it to false when it’s done.

And then we call useEffect with a callback that calls getData to make the request when the getData function is defined.

Finally, we return the data and loading state values in an object.

Once we define the useFetch hook, we can use it directly in a component.

To do this, we write:

export default function App() {
  const { data, loading } = useFetch("");

  return (
    <div className="App">{loading ? "loading" : JSON.stringify(data)}</div>

We call useFetch with the URL we want to make the request to.

And we destructure the returned object into the data and loading variables.

Then we render the response as a JSON string if loading is false and 'loading' otherwise.

Create a Custom Debounce React Hook

Likewise, we can create our own custom debounce React hook with the same method.

Debouncing will add a delay before we do something. So when we want to debounce an input, we delay setting the value as a state’s value by a short time period.

To do this, we create the useDebounce hook by writing:

import { useEffect, useRef, useState } from "react";

const useDebounce = (value, delay = 500) => {
  const [debouncedValue, setDebouncedValue] = useState("");
  const timerRef = useRef();

  useEffect(() => {
    timerRef.current = setTimeout(() => setDebouncedValue(value), delay);

    return () => {
  }, [value, delay]);

  return debouncedValue;

Like the useFetch hook, we create the useDebounce hook by defining a function with the given name. It takes the input value and the delay for the debounce time period in milliseconds. In it, we call the useState hook to define the debouncedValue state.

And we call useEffect with a callback that calls setTimeout with a callback that calls setDebouncedValue when value changes or after a delay in milliseconds.

The returned timer ID number is assigned to the timerRef's current value.

The useRef hook creates a ref, which is a value that can be kept even after the component or hook is rendered or rerun.

We also return a function that calls clearTimeout to clear the timer when value or delay is changed.

Finally, we return the debouncedValue so we can use it in our component.

Then, to use useDebounce in our component, we write:

export default function App() {
  const [value, setValue] = useState("");
  const debouncedValue = useDebounce(value, 500);

  const search = useCallback(async () => {
    const res = await fetch(
    const json = await res.json();
  }, [debouncedValue]);

  useEffect(() => {
  }, [debouncedValue, search]);

  return (
    <div className="App">
      <input value={value} onChange={(e) => setValue(} />

We define the value state with the useState hook.

Next, we call the useDebounce hook we created with the value state and a 500 millisecond delay before returning the debouncedValue.

And then we define the search function that makes a get request with fetch to get data from an API.

We then get the JSON response body with res.json and then log that value.

The function is used as an argument of the useCallback hook so we can avoid redefining the function when the debouncedValue value didn’t change.

Then we call the useEffect hook with a callback that calls the search function when debouncedValue or the search function has changed.

Finally, we add an input that sets the value prop to the value state and the onChange prop to a function that calls setValue to set value to the input value.

As a result, when we type in something into the input, the debouncedValue—which is set to value—is returned after the delay we specified as the second argument of useDebounce.

And then the useEffect callback is called to make the request with the latest debouncedValue.


One common feature that is implemented in web frontend apps is adding debounce for inputs. Debouncing lets us delay actions after an input’s value is changed.

We can make our own debounce hook so we can easily do some action when we change an input’s value after a delay.

About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at and the author of Vue.js 3 By Example.

Related Posts


Comments are disabled in preview mode.