πŸš€ Smarter Debouncing in React β€” Introducing `@er-raj-aryan/use-smart-debounce`



This content originally appeared on DEV Community and was authored by Raj Aryan

Tired of making a dozen API calls every time a user types three letters?
Meet @er-raj-aryan/use-smart-debounce β€” a lightweight React hook library that makes debouncing async-safe, TypeScript-ready, and smooth like butter 🧈

👉 NPM: @er-raj-aryan/use-smart-debounce
👉 GitHub: https://github.com/er-raj-aryan/use-smart-debounce

💡 Why I Built This

While building a Next.js dashboard, I ran into the same old issue β€” API calls firing on every keystroke during search input.
Existing solutions like lodash.debounce or use-debounce didn’t handle async calls, cancellation, or stale responses well.

So I decided to build something that does:

  • Cancels stale API requests
  • Handles async promises safely
  • Works with leading, trailing, and maxWait modes
  • Comes with full TypeScript support
  • Ships with zero dependencies

⚙ Installation

npm i @er-raj-aryan/use-smart-debounce
# or
yarn add @er-raj-aryan/use-smart-debounce

🧩 What’s Inside

Hook Use Case Description
useDebouncedValue Debounce values Returns a delayed version of any state value
useDebouncedCallback Debounce functions Debounce any callback with leading/trailing control
useDebouncedAsync Debounce async calls Cancels in-flight requests & ignores stale responses

🧠 Basic Example β€” Debounce a Value

import { useDebouncedValue } from "@er-raj-aryan/use-smart-debounce";
import { useState, useEffect } from "react";

export default function SearchBox() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebouncedValue(query, 500);

  useEffect(() => {
    if (debouncedQuery.length >= 3) {
      console.log("Search:", debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Type to search…"
    />
  );
}

✅ Fires only after 500ms of inactivity
✅ Perfect for live search or filtering

⚡ Debounce Callbacks with Control

import { useDebouncedCallback } from "@er-raj-aryan/use-smart-debounce";

function ResizeTracker() {
  const debouncedResize = useDebouncedCallback(
    () => console.log("Window resized:", window.innerWidth),
    300,
    { leading: false, trailing: true }
  );

  useEffect(() => {
    window.addEventListener("resize", debouncedResize);
    return () => window.removeEventListener("resize", debouncedResize);
  }, []);

  return <p>Resize the window to see it in action</p>;
}

🪄 Async-Safe Debouncing

This is where most debounce hooks fail β€” multiple async calls return out of order, and the old response overwrites the new one.
useDebouncedAsync handles that for you:

import { useDebouncedAsync } from "@er-raj-aryan/use-smart-debounce";
import { useState, useEffect } from "react";

function LiveSearch() {
  const [query, setQuery] = useState("");

  const { run, status, data, error } = useDebouncedAsync(
    async (q: string) => {
      if (q.length < 3) return [];
      const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
      const json = await res.json();
      return json.results ?? [];
    },
    500
  );

  useEffect(() => {
    run(query);
  }, [query]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {status === "loading" && <p>Loading…</p>}
      {error && <p style={{ color: "red" }}>Error fetching</p>}
      <ul>
        {Array.isArray(data) &&
          data.map((r: any) => <li key={r.id}>{r.name}</li>)}
      </ul>
    </div>
  );
}

✨ Features:

  • Cancels the previous API call when user keeps typing
  • Prevents stale results from overwriting fresh ones
  • Tracks status, data, and error

🧮 Real Example β€” HS Code Lookup with Material UI

Here’s a practical example using MUI’s <Autocomplete> with your HS Code API (/db/hs_code_list/?search_key=):

import { Autocomplete, TextField } from "@mui/material";
import { useDebouncedAsync } from "@er-raj-aryan/use-smart-debounce";
import { useState, useEffect } from "react";

type HS = { hs_code: string; description: string };

export default function HSCodeSearch() {
  const [value, setValue] = useState<HS | null>(null);
  const [options, setOptions] = useState<HS[]>([]);

  const { run, status, data } = useDebouncedAsync(
    async (q: string) => {
      if (q.length < 3 || !/^\d+$/.test(q)) return [];
      const res = await fetch(`/db/hs_code_list/?search_key=${q}`);
      const json = await res.json();
      return json.results ?? [];
    },
    600
  );

  useEffect(() => {
    if (Array.isArray(data)) setOptions(data);
  }, [data]);

  return (
    <Autocomplete
      size="small"
      options={options}
      value={value}
      onChange={(_, v) => setValue(v)}
      getOptionLabel={(o) => o.hs_code}
      onInputChange={(_, v) => run(v)}
      renderInput={(params) => (
        <TextField
          {...params}
          label="HS Code"
          helperText={
            value?.description ||
            (status === "loading" ? "Searching..." : "")
          }
        />
      )}
    />
  );
}

⚖ Comparison Table

Feature @er-raj-aryan/use-smart-debounce use-debounce ahooks lodash.debounce
🧠 TypeScript support ✅ Native ✅ ✅ ❌
⚙ Async-safe ✅ Cancels + ignores stale ❌ ⚠ Partial ❌
🔁 Leading/Trailing ✅ ⚠ Partial ✅ ✅
🌀 Race protection ✅ Yes ❌ ❌ ❌
⚡ Bundle size <3 KB ~4 KB ~200 KB 24 KB
🧩 Dependencies 0 0 20+ 1
🧰 Designed for React ✅ Hooks ✅ Hooks ✅ ❌

🚀 Why You’ll Love It

  • No dependencies β†’ ultra-fast build
  • Tiny footprint β†’ great for production apps
  • Async-safe β†’ ideal for API-driven UIs
  • TypeScript-ready β†’ works out of the box

🏁 Wrap Up

If you’re building React apps that rely on API queries, form inputs, or live filters β€”
@er-raj-aryan/use-smart-debounce will save you time, requests, and user frustration.

👉 Install now:

npm i @er-raj-aryan/use-smart-debounce

🔗 Links:

🧑‍💻 Author: Er Raj Aryan
Frontend Engineer | React / Next.js Developer | Open Source Enthusiast

If you found this useful β€” ⭐ the repo or drop a comment!
Let’s build smarter UIs together 💙


This content originally appeared on DEV Community and was authored by Raj Aryan