Creating a Reusable useDebounce Hook
A practical implementation of a debounce hook for text inputs:
const useDebounce = (callback, delay) => {
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
return useMemo(() => {
const debouncedFn = (...args) => {
if (debouncedFn.timeout) {
clearTimeout(debouncedFn.timeout);
}
debouncedFn.timeout = setTimeout(() => {
callbackRef.current(...args);
}, delay);
};
return debouncedFn;
}, [delay]);
};
// Usage for Search Input
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const searchAPI = async (value) => {
const data = await fetch(`/api/search?q=${value}`);
setResults(await data.json());
};
const debouncedSearch = useDebounce(searchAPI, 300);
return (
<div>
<input
type="text"
value={query}
onChange={e => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
}}
/>
<SearchResults results={results} />
</div>
);
};
Implementing useThrottle for Scroll Events
A throttle hook for handling scroll events:
const useThrottle = (callback, limit) => {
const callbackRef = useRef(callback);
const throttlingRef = useRef(false);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
return useMemo(() => {
const throttledFn = (...args) => {
if (!throttlingRef.current) {
callbackRef.current(...args);
throttlingRef.current = true;
setTimeout(() => {
throttlingRef.current = false;
}, limit);
}
};
return throttledFn;
}, [limit]);
};
// Usage for Infinite Scroll
const InfiniteScroll = () => {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const loadMore = async () => {
const data = await fetch(`/api/items?page=${page}`);
const newItems = await data.json();
setItems(prev => [...prev, ...newItems]);
setPage(p => p + 1);
};
const handleScroll = (e) => {
const { scrollTop, clientHeight, scrollHeight } = e.target;
if (scrollHeight - scrollTop <= clientHeight * 1.5) {
loadMore();
}
};
const throttledScroll = useThrottle(handleScroll, 500);
return (
<div onScroll={throttledScroll} style={{ height: '400px', overflow: 'auto' }}>
{items.map(item => (
<ItemCard key={item.id} {...item} />
))}
</div>
);
};
Form Submission with Debounce
Prevent multiple form submissions:
const DebouncedForm = () => {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const submitForm = async (data) => {
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data)
});
};
const debouncedSubmit = useDebounce(submitForm, 1000);
const handleSubmit = (e) => {
e.preventDefault();
debouncedSubmit(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.name}
onChange={e => setFormData(prev => ({
...prev,
name: e.target.value
}))}
/>
<input
type="email"
value={formData.email}
onChange={e => setFormData(prev => ({
...prev,
email: e.target.value
}))}
/>
<button type="submit">Submit</button>
</form>
);
};
Window Resize with Throttle
Handle window resize events efficiently:
const ResponsiveLayout = () => {
const [width, setWidth] = useState(window.innerWidth);
const handleResize = () => {
setWidth(window.innerWidth);
};
const throttledResize = useThrottle(handleResize, 200);
useEffect(() => {
window.addEventListener('resize', throttledResize);
return () => window.removeEventListener('resize', throttledResize);
}, []);
return (
<div>
{width > 768 ? (
<DesktopLayout />
) : (
<MobileLayout />
)}
</div>
);
};
Cleanup and Memory Management
Always clean up timers to prevent memory leaks:
const useDebouncedEffect = (callback, deps, delay) => {
useEffect(() => {
const handler = setTimeout(() => {
callback();
}, delay);
return () => {
clearTimeout(handler);
};
}, [...deps, delay]);
};
// Usage
const AutoSaveComponent = () => {
const [content, setContent] = useState('');
useDebouncedEffect(
() => {
saveToServer(content);
},
[content],
1000
);
return (
<textarea
value={content}
onChange={e => setContent(e.target.value)}
placeholder="Auto-saves after 1 second of inactivity"
/>
);
};
Key Takeaways
- Use debounce for text inputs and form submissions
- Use throttle for scroll and resize events
- Always clean up timers in useEffect
- Use refs to keep callbacks up to date
- Consider performance impact when choosing delay times