Skip to content

Commit 99d2315

Browse files
Merge pull request #6 from Tracktor/feat/use-debounce
feat: add useDebouncedFn hook for debouncing functions in React components
2 parents 66c7581 + 04958db commit 99d2315

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# [Versions](https://github.com/Tracktor/react-utils/releases)
22

3-
## v1.24.2
4-
- **[chore]** : Migrate to Bun & GH Actions for CI/CD workflows.
3+
## v1.25.0
4+
- **[feat]** : Add `useDebouncedFn` hook for creating debounced functions in React components. (#123)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@tracktor/react-utils",
33
"description": "React data table and react data grid",
4-
"version": "1.24.2",
4+
"version": "1.25.0",
55
"private": false,
66
"license": "ISC",
77
"type": "module",
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useCallback, useEffect, useRef } from "react";
2+
3+
/**
4+
* Creates a debounced version of a callback function that delays invoking the function
5+
* until after the specified delay has elapsed since the last time it was invoked.
6+
*
7+
* This hook is useful for optimizing performance by limiting the rate at which a function
8+
* can fire, particularly useful for:
9+
* - Search input handlers
10+
* - Auto-save functionality
11+
* - API calls triggered by user input
12+
* - Window resize/scroll handlers
13+
*
14+
* @example
15+
* const debouncedSearch = useDebouncedCallback((query: string) => {
16+
* fetchSearchResults(query);
17+
* }, 500);
18+
*
19+
* // Will only execute after user stops typing for 500ms
20+
* debouncedSearch(inputValue);
21+
*/
22+
const useDebouncedFn = <T extends (...args: never[]) => unknown>(callback: T, delay = 300): ((...args: Parameters<T>) => void) => {
23+
// Store the latest callback to avoid stale closures
24+
const callbackRef = useRef(callback);
25+
callbackRef.current = callback;
26+
27+
// Store the timeout ID for cleanup
28+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
29+
30+
// Create the debounced function (only recreates if delay changes)
31+
const debouncedCallback = useCallback(
32+
(...args: Parameters<T>) => {
33+
// Clear any existing timeout
34+
if (timeoutRef.current !== null) {
35+
clearTimeout(timeoutRef.current);
36+
}
37+
38+
// Set a new timeout to execute the callback after the delay
39+
timeoutRef.current = setTimeout(() => {
40+
callbackRef.current(...args);
41+
}, delay);
42+
},
43+
[delay],
44+
);
45+
46+
// Cleanup: clear timeout when component unmounts
47+
useEffect(() => {
48+
return () => {
49+
if (timeoutRef.current !== null) {
50+
clearTimeout(timeoutRef.current);
51+
}
52+
};
53+
}, []);
54+
55+
return debouncedCallback;
56+
};
57+
58+
export default useDebouncedFn;

src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
export * from "@/hooks/useDebounce/useDebounce";
44
export { default as useDebounce } from "@/hooks/useDebounce/useDebounce";
5+
export * from "@/hooks/useDebounce/useDebouncedFn";
6+
export { default as useDebouncedFn } from "@/hooks/useDebounce/useDebouncedFn";
57
export * from "@/hooks/useDocumentTitle/useDocumentTitle";
68
export { default as useDocumentTitle } from "@/hooks/useDocumentTitle/useDocumentTitle";
79
export * from "@/hooks/useEventListener/useEventListener";

0 commit comments

Comments
 (0)