Rooks
Lifecycle & Effects

useDebouncedEffect

About

A version of useEffect that debounces the effect execution based on dependency changes. This hook is useful when you want to delay the execution of an effect until dependencies have stopped changing for a specified duration.

Common use cases include:

  • Delaying API calls until the user stops typing in a search input
  • Waiting for resize events to settle before performing expensive calculations
  • Debouncing form validation

Examples

Basic usage with search input

import { useDebouncedEffect } from "rooks";
import { useState } from "react";
 
export default function SearchComponent() {
  const [searchQuery, setSearchQuery] = useState("");
  const [results, setResults] = useState([]);
 
  useDebouncedEffect(
    () => {
      if (searchQuery.trim()) {
        // This will only run 500ms after the user stops typing
        fetch(`/api/search?q=${searchQuery}`)
          .then(res => res.json())
          .then(data => setResults(data));
      }
    },
    [searchQuery],
    500
  );
 
  return (
    <div>
      <input
        type="text"
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

With cleanup function

import { useDebouncedEffect } from "rooks";
import { useState } from "react";
 
export default function SubscriptionComponent({ topic }) {
  const [messages, setMessages] = useState([]);
 
  useDebouncedEffect(
    () => {
      const subscription = subscribeToTopic(topic, (message) => {
        setMessages(prev => [...prev, message]);
      });
 
      // Return cleanup function
      return () => {
        subscription.unsubscribe();
      };
    },
    [topic],
    300
  );
 
  return (
    <div>
      {messages.map((msg, i) => (
        <p key={i}>{msg}</p>
      ))}
    </div>
  );
}

With leading option

import { useDebouncedEffect } from "rooks";
import { useState } from "react";
 
export default function ImmediateSearchComponent() {
  const [searchQuery, setSearchQuery] = useState("");
 
  useDebouncedEffect(
    () => {
      // With leading: true, this runs immediately on first change
      // then debounces subsequent changes
      console.log("Searching for:", searchQuery);
    },
    [searchQuery],
    500,
    { leading: true }
  );
 
  return (
    <input
      type="text"
      value={searchQuery}
      onChange={(e) => setSearchQuery(e.target.value)}
    />
  );
}

With multiple dependencies

import { useDebouncedEffect } from "rooks";
import { useState } from "react";
 
export default function FilteredListComponent() {
  const [category, setCategory] = useState("all");
  const [sortBy, setSortBy] = useState("name");
  const [items, setItems] = useState([]);
 
  useDebouncedEffect(
    () => {
      // Runs 300ms after either category or sortBy stops changing
      fetch(`/api/items?category=${category}&sort=${sortBy}`)
        .then(res => res.json())
        .then(data => setItems(data));
    },
    [category, sortBy],
    300
  );
 
  return (
    <div>
      <select value={category} onChange={(e) => setCategory(e.target.value)}>
        <option value="all">All</option>
        <option value="electronics">Electronics</option>
        <option value="books">Books</option>
      </select>
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
        <option value="name">Name</option>
        <option value="price">Price</option>
      </select>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Arguments

ArgumentTypeDescriptionDefault
effectEffectCallbackThe effect callback to run (can return a cleanup function)Required
depsDependencyListArray of dependencies that trigger effect re-executionRequired
delaynumberThe debounce delay in milliseconds500
optionsDebounceSettingsOptional debounce settingsundefined

Options

The options parameter accepts the following settings:

OptionTypeDescriptionDefault
leadingbooleanExecute on the leading edge of the timeoutfalse
trailingbooleanExecute on the trailing edge of the timeouttrue
maxWaitnumberMaximum time the effect can be delayed (milliseconds)-

Key Features

  • Automatic Debouncing: Effect execution is delayed until dependencies stop changing
  • Cleanup Support: Return a cleanup function from the effect, just like useEffect
  • Configurable Timing: Customize delay and leading/trailing execution
  • Cancellation on Unmount: Pending effects are cancelled when the component unmounts
  • Dependency Tracking: Works just like useEffect with dependency arrays

On this page