Overview
The useDebounce hook delays updating a value until after a specified delay period has passed without the value changing. This is essential for optimizing search inputs and preventing excessive API calls.
Source: src/hooks/useDebounce.js:3
Function Signature
export const useDebounce = ( value , delay = 500 ) => {
const [ debouncedValue , setDebouncedValue ] = useState ( value );
useEffect (() => {
const timer = setTimeout (() => {
setDebouncedValue ( value );
}, delay );
return () => clearTimeout ( timer );
}, [ value , delay ]);
return debouncedValue ;
};
Parameters
The value to debounce (commonly a search string or user input)
Delay in milliseconds before updating the debounced value. Defaults to 500ms.
Return Value
The debounced version of the input value. Only updates after the delay period has elapsed without changes.
How It Works
User types in an input field → value changes rapidly
Hook sets a timer for the specified delay
If value changes again before timer completes → timer resets
Once user stops typing for delay milliseconds → debouncedValue updates
Cleanup function cancels pending timers on unmount
This prevents API calls or expensive operations from firing on every keystroke. Instead, they only fire once the user has stopped typing.
Usage Examples
Search Filter (Real Implementation)
Search with API Call
Auto-save Form
Window Resize Handler
import { useState , useEffect } from "react" ;
import { useDebounce } from "../../hooks/useDebounce" ;
const Filters = ({ setSearchParams }) => {
const [ searchInput , setSearchInput ] = useState ( "" );
const debouncedSearch = useDebounce ( searchInput , 500 );
useEffect (() => {
setSearchParams (( prevParams ) => {
const params = new URLSearchParams ( prevParams );
if ( debouncedSearch ) {
params . set ( "search" , debouncedSearch );
} else {
params . delete ( "search" );
}
params . set ( "page" , "1" );
return params ;
});
}, [ debouncedSearch , setSearchParams ]);
return (
< input
type = "text"
className = "form-control"
placeholder = "Search products..."
value = { searchInput }
onChange = { ( e ) => setSearchInput ( e . target . value ) }
/>
);
};
Without Debouncing
// User types "laptop" (6 characters)
// Result: 6 API calls fired
L → API call
La → API call
Lap → API call
Lapt → API call
Lapto → API call
Laptop → API call
With Debouncing (500ms)
// User types "laptop" (6 characters)
// Result: 1 API call fired (500ms after user stops typing)
L
La
Lap
Lapt
Lapto
Laptop → [ wait 500 ms ] → API call
This reduces API calls by up to 83% in this example, significantly improving performance and reducing server load.
Delay Recommendations
Search inputs : 300-500ms (good balance between responsiveness and performance)
Auto-save : 1000-2000ms (prevent too frequent saves)
Window resize : 100-200ms (quick enough to feel responsive)
Typing indicators : 300-500ms
Real-World Implementation
From the Filters component (src/components/filters/Filters.jsx:13):
const Filters = ({ searchParam , setSearchParams }) => {
const [ searchInput , setSearchInput ] = useState ( searchParam );
const debouncedSearch = useDebounce ( searchInput , 500 );
useEffect (() => {
// Only update URL params after user stops typing
setSearchParams (( prevParams ) => {
const params = new URLSearchParams ( prevParams );
if ( debouncedSearch ) params . set ( "search" , debouncedSearch );
else params . delete ( "search" );
return params ;
});
}, [ debouncedSearch , setSearchParams ]);
return (
< input
type = "text"
value = { searchInput }
onChange = { ( e ) => setSearchInput ( e . target . value ) }
placeholder = "Search products..."
/>
);
};
Cleanup and Memory Management
The hook automatically cleans up pending timers when:
The component unmounts
The value changes before the delay completes
The delay parameter changes
This prevents memory leaks and ensures timers don’t fire after component unmount.
TypeScript Usage
If using TypeScript, you can add type safety:
export const useDebounce = < T ,>( value : T , delay : number = 500 ) : T => {
const [ debouncedValue , setDebouncedValue ] = useState < T >( value );
useEffect (() => {
const timer = setTimeout (() => {
setDebouncedValue ( value );
}, delay );
return () => clearTimeout ( timer );
}, [ value , delay ]);
return debouncedValue ;
};
Common Patterns
Pattern 1: Search with URL Params
const [ searchInput , setSearchInput ] = useState ( "" );
const debouncedSearch = useDebounce ( searchInput , 500 );
useEffect (() => {
updateURLParams ({ search: debouncedSearch });
}, [ debouncedSearch ]);
Pattern 2: API Query Trigger
const [ query , setQuery ] = useState ( "" );
const debouncedQuery = useDebounce ( query , 300 );
const { data } = useQuery ({
queryKey: [ "search" , debouncedQuery ],
queryFn : () => searchAPI ( debouncedQuery ),
enabled: debouncedQuery . length > 2 ,
});
const [ email , setEmail ] = useState ( "" );
const debouncedEmail = useDebounce ( email , 500 );
useEffect (() => {
validateEmail ( debouncedEmail );
}, [ debouncedEmail ]);
useProducts - Often used together for search functionality
useCategories - Category filtering that may benefit from debouncing