A React hook for debouncing callback functions
npm install @usefy/use-debounce-callback

A powerful React hook for debounced callbacks with cancel, flush, and pending methods
Installation •
Quick Start •
API Reference •
Examples •
License
---
@usefy/use-debounce-callback provides a debounced version of your callback function with full control methods: cancel(), flush(), and pending(). Perfect for API calls, form submissions, event handlers, and any scenario requiring debounced function execution with fine-grained control.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with generics and exported interfaces
- Full Control — cancel(), flush(), and pending() methods
- Flexible Options — Leading edge, trailing edge, and maxWait support
- SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
- Lightweight — Minimal bundle footprint (~500B minified + gzipped)
- Well Tested — Comprehensive test coverage with Vitest
---
``bashnpm
npm install @usefy/use-debounce-callback
$3
This package requires React 18 or 19:
`json
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}
`---
Quick Start
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function SearchInput() {
const [query, setQuery] = useState("");
const debouncedSearch = useDebounceCallback((searchTerm: string) => {
fetchSearchResults(searchTerm);
}, 300);
const handleChange = (e: React.ChangeEvent) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
};
return (
type="text"
value={query}
onChange={handleChange}
placeholder="Search..."
/>
);
}
`---
API Reference
$3
A hook that returns a debounced version of the provided callback function.
#### Parameters
| Parameter | Type | Default | Description |
| ---------- | ----------------------------------- | ------- | ---------------------------------- |
|
callback | T extends (...args: any[]) => any | — | The callback function to debounce |
| delay | number | 500 | The debounce delay in milliseconds |
| options | UseDebounceCallbackOptions | {} | Additional configuration options |#### Options
| Option | Type | Default | Description |
| ---------- | --------- | ------- | ---------------------------------------------- |
|
leading | boolean | false | Invoke on the leading edge (first call) |
| trailing | boolean | true | Invoke on the trailing edge (after delay) |
| maxWait | number | — | Maximum time to wait before forcing invocation |#### Returns
DebouncedFunction| Property | Type | Description |
| ----------- | --------------- | --------------------------------------------------- |
|
(...args) | ReturnType | The debounced function (same signature as original) |
| cancel | () => void | Cancels any pending invocation |
| flush | () => void | Immediately invokes any pending invocation |
| pending | () => boolean | Returns true if there's a pending invocation |---
Examples
$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function Editor() {
const [content, setContent] = useState("");
const debouncedSave = useDebounceCallback((text: string) => {
saveToServer(text);
console.log("Auto-saved");
}, 1000);
const handleChange = (e: React.ChangeEvent) => {
setContent(e.target.value);
debouncedSave(e.target.value);
};
const handleManualSave = () => {
// Flush any pending save immediately
debouncedSave.flush();
};
const handleDiscard = () => {
// Cancel pending save and reset content
debouncedSave.cancel();
setContent("");
};
return (
{debouncedSave.pending() && Saving...}
);
}
`$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function SearchWithSuggestions() {
const [results, setResults] = useState([]);
// First keystroke triggers immediate search, then debounce
const debouncedSearch = useDebounceCallback(
async (query: string) => {
const data = await fetch(
/api/search?q=${query});
setResults(await data.json());
},
300,
{ leading: true }
); return (
type="text"
onChange={(e) => debouncedSearch(e.target.value)}
placeholder="Search..."
/>
);
}
`$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function RegistrationForm() {
const [email, setEmail] = useState("");
const [error, setError] = useState("");
const validateEmail = useDebounceCallback(async (value: string) => {
if (!value.includes("@")) {
setError("Invalid email format");
return;
}
const response = await fetch(
/api/check-email?e=${value});
const { available } = await response.json();
setError(available ? "" : "Email already registered");
}, 500); const handleChange = (e: React.ChangeEvent) => {
setEmail(e.target.value);
setError(""); // Clear error immediately
validateEmail(e.target.value);
};
return (
type="email"
value={email}
onChange={handleChange}
placeholder="Enter email"
/>
{error && {error}}
);
}
`$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function ResizeHandler() {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
// Debounce resize events, but guarantee update every 1 second
const handleResize = useDebounceCallback(
() => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
},
250,
{ maxWait: 1000 }
);
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => {
handleResize.cancel();
window.removeEventListener("resize", handleResize);
};
}, [handleResize]);
return (
Window: {dimensions.width} x {dimensions.height}
);
}
`$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const fetchData = useDebounceCallback(async (params: QueryParams) => {
setLoading(true);
try {
const response = await fetch("/api/data", {
method: "POST",
body: JSON.stringify(params),
});
setData(await response.json());
} finally {
setLoading(false);
}
}, 500);
return (
{loading && }
);
}
`$3
`tsx
import { useDebounceCallback } from "@usefy/use-debounce-callback";function Component() {
const debouncedAction = useDebounceCallback(() => {
// Some action
}, 500);
// Cancel pending on unmount
useEffect(() => {
return () => {
debouncedAction.cancel();
};
}, [debouncedAction]);
return ;
}
`---
TypeScript
This hook is written in TypeScript with full generic support.
`tsx
import {
useDebounceCallback,
type UseDebounceCallbackOptions,
type DebouncedFunction,
} from "@usefy/use-debounce-callback";// Type inference from callback
const debouncedFn = useDebounceCallback((a: string, b: number) => {
return
${a}-${b};
}, 300);// debouncedFn(string, number) => string | undefined
// debouncedFn.cancel() => void
// debouncedFn.flush() => void
// debouncedFn.pending() => boolean
``---
This package maintains comprehensive test coverage to ensure reliability and stability.
📊 View Detailed Coverage Report (GitHub Pages)
Control Method Tests
- Cancel pending invocations
- Flush immediately invokes pending callback
- pending() returns correct state
- cancel() clears pending state
- flush() clears pending state after invocation
Leading/Trailing Edge Tests
- Invoke on leading edge with leading: true
- No immediate invoke with leading: false (default)
- Invoke on trailing edge with trailing: true (default)
- No trailing invoke with trailing: false
- Combined leading and trailing options
---
MIT © mirunamu
This package is part of the usefy monorepo.
---
Built with care by the usefy team