useDebounce
Delay value updates to reduce unnecessary operations.
Use Cases
- Search input that triggers API calls
- Form validation on input change
- Window resize handlers
- Scroll position tracking
Code
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number = 300): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}Usage
Search Input
import { useState } from 'react';
import { useDebounce } from '@/hooks/useDebounce';
import { useQuery } from '@tanstack/react-query';
function SearchInput() {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);
const { data, isLoading } = useQuery({
queryKey: ['search', debouncedSearch],
queryFn: () => searchAPI(debouncedSearch),
enabled: debouncedSearch.length > 0,
});
return (
<div>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search..."
/>
{isLoading && <span>Loading...</span>}
{data?.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}Form Validation
function EmailInput() {
const [email, setEmail] = useState('');
const debouncedEmail = useDebounce(email, 500);
const [isValid, setIsValid] = useState(true);
useEffect(() => {
if (debouncedEmail) {
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(debouncedEmail);
setIsValid(valid);
}
}, [debouncedEmail]);
return (
<div>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
{!isValid && <span className="text-red-500">Invalid email</span>}
</div>
);
}Variants
useDebounceCallback
For debouncing functions instead of values:
import { useCallback, useRef } from 'react';
export function useDebounceCallback<T extends (...args: any[]) => any>(
callback: T,
delay: number = 300
): T {
const timeoutRef = useRef<NodeJS.Timeout>();
return useCallback(
((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}) as T,
[callback, delay]
);
}Usage:
const debouncedSave = useDebounceCallback((value: string) => {
saveToServer(value);
}, 1000);
<input onChange={(e) => debouncedSave(e.target.value)} />