useDebounce Hook in React
Optimizing User Input with Debouncing in React using a Reusable Custom Hook When dealing with user input, particularly in search fields or during API calls, updating the state on every keystroke can lead to performance problems. A debounce function helps by delaying execution until a specified period of time has passed since the last input. In this article, we’ll first show how to create a component without using a custom debounce hook and then refactor it to utilize a reusable useDebounce hook. Implementing Debouncing Without a Custom Hook Let's start by creating a simple React component that processes user input in real time without debouncing. import React, { useState, useEffect } from "react"; const App = () => { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); useEffect(() => { if (query) { fetch(`https://demo.dataverse.org/api/search?q=${query}`) .then((res) => res.json()) .then(({ data }) => setResults(data.items)); } }, [query]); return ( Search setQuery(e.target.value)} placeholder="Type to search..." /> {results.map((item, index) => ( {item.name} ))} ); }; export default App; Issues with This Approach While the component works, it has a few drawbacks: Excessive API calls: A request is sent with every keystroke, which leads to unnecessary API calls. Performance problems: Frequent state updates and re-renders can slow down the user interface. Lack of control: There is no control over when the search is triggered. Creating a Custom useDebounce Hook To improve performance, we'll create a useDebounce hook that delays updating the state until the user stops typing for a specified duration. The useDebounce Hook Here’s how to create a custom hook to debounce a value: import { useState, useEffect } from "react"; const useDebounce = (value, delay) => { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; }; export default useDebounce; Advantages of the useDebounce Hook Reduced API calls: The request is triggered only after the user stops typing, reducing unnecessary network calls. Improved performance: It prevents multiple state updates and unnecessary re-renders. Reusability: The useDebounce hook can be reused across various components. Refactoring the Component to Use useDebounce Let’s now refactor the search component to use the useDebounce hook. import React, { useState, useEffect } from "react"; import useDebounce from "./useDebounce"; const SearchBar = () => { const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 500); const [results, setResults] = useState([]); useEffect(() => { if (debouncedQuery) { fetch(`https://demo.dataverse.org/api/search?q=${query}`) .then((res) => res.json()) .then(({ data }) => setResults(data.items)); } }, [debouncedQuery]); return ( Search setQuery(e.target.value)} placeholder="Type to search..." /> {results.map((item, index) => ( {item.name} ))} ); }; export default SearchBar; Key Benefits of This Refactoring Prevents unnecessary API calls: The request is sent only when the user stops typing, thus avoiding redundant API calls. Enhanced performance: State updates and re-renders are minimized, improving UI responsiveness. Cleaner code: The useDebounce hook keeps the component logic simple and maintainable. Additional Use Cases for useDebounce The useDebounce hook can be used in several different scenarios. Here are a few examples: 1. Debouncing Input Fields in Forms When handling form input, you might want to debounce the input to prevent unnecessary validations or actions from firing on every keystroke. import React, { useState } from "react"; import useDebounce from "./useDebounce"; const DebouncedInput = () => { const [input, setInput] = useState(""); const debouncedInput = useDebounce(input, 500); return ( Debounced Input setInput(e.target.value)} placeholder="Type something..." /> Processed Value: {debouncedInput} ); }; export default DebouncedInput; This prevents validation logic from running on every keystroke, reducing unnecessary computations and improving responsiveness. 2. Handling Window Resize Events Debouncing can also be useful to optimize expensive operations triggered by window resizing, such as recalculating layouts or updating UI components. import React, { useState, useEffect } from "react"; impor

Optimizing User Input with Debouncing in React using a Reusable Custom Hook
When dealing with user input, particularly in search fields or during API calls, updating the state on every keystroke can lead to performance problems. A debounce function helps by delaying execution until a specified period of time has passed since the last input. In this article, we’ll first show how to create a component without using a custom debounce hook and then refactor it to utilize a reusable useDebounce
hook.
Implementing Debouncing Without a Custom Hook
Let's start by creating a simple React component that processes user input in real time without debouncing.
import React, { useState, useEffect } from "react";
const App = () => {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
if (query) {
fetch(`https://demo.dataverse.org/api/search?q=${query}`)
.then((res) => res.json())
.then(({ data }) => setResults(data.items));
}
}, [query]);
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<h2>Searchh2>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Type to search..."
/>
<ul>
{results.map((item, index) => (
<li key={index}>{item.name}li>
))}
ul>
div>
);
};
export default App;
Issues with This Approach
While the component works, it has a few drawbacks:
- Excessive API calls: A request is sent with every keystroke, which leads to unnecessary API calls.
- Performance problems: Frequent state updates and re-renders can slow down the user interface.
- Lack of control: There is no control over when the search is triggered.
Creating a Custom useDebounce
Hook
To improve performance, we'll create a useDebounce
hook that delays updating the state until the user stops typing for a specified duration.
The useDebounce
Hook
Here’s how to create a custom hook to debounce a value:
import { useState, useEffect } from "react";
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
export default useDebounce;
Advantages of the useDebounce
Hook
- Reduced API calls: The request is triggered only after the user stops typing, reducing unnecessary network calls.
- Improved performance: It prevents multiple state updates and unnecessary re-renders.
-
Reusability: The
useDebounce
hook can be reused across various components.
Refactoring the Component to Use useDebounce
Let’s now refactor the search component to use the useDebounce
hook.
import React, { useState, useEffect } from "react";
import useDebounce from "./useDebounce";
const SearchBar = () => {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500);
const [results, setResults] = useState([]);
useEffect(() => {
if (debouncedQuery) {
fetch(`https://demo.dataverse.org/api/search?q=${query}`)
.then((res) => res.json())
.then(({ data }) => setResults(data.items));
}
}, [debouncedQuery]);
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<h2>Searchh2>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Type to search..."
/>
<ul>
{results.map((item, index) => (
<li key={index}>{item.name}li>
))}
ul>
div>
);
};
export default SearchBar;
Key Benefits of This Refactoring
- Prevents unnecessary API calls: The request is sent only when the user stops typing, thus avoiding redundant API calls.
- Enhanced performance: State updates and re-renders are minimized, improving UI responsiveness.
-
Cleaner code: The
useDebounce
hook keeps the component logic simple and maintainable.
Additional Use Cases for useDebounce
The useDebounce
hook can be used in several different scenarios. Here are a few examples:
1. Debouncing Input Fields in Forms
When handling form input, you might want to debounce the input to prevent unnecessary validations or actions from firing on every keystroke.
import React, { useState } from "react";
import useDebounce from "./useDebounce";
const DebouncedInput = () => {
const [input, setInput] = useState("");
const debouncedInput = useDebounce(input, 500);
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<h2>Debounced Inputh2>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type something..."
/>
<p>Processed Value: {debouncedInput}p>
div>
);
};
export default DebouncedInput;
This prevents validation logic from running on every keystroke, reducing unnecessary computations and improving responsiveness.
2. Handling Window Resize Events
Debouncing can also be useful to optimize expensive operations triggered by window resizing, such as recalculating layouts or updating UI components.
import React, { useState, useEffect } from "react";
import useDebounce from "./useDebounce";
const ResizeComponent = () => {
const [width, setWidth] = useState(window.innerWidth);
const debouncedWidth = useDebounce(width, 300);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>Window Width: {debouncedWidth}pxp>;
};
export default ResizeComponent;
This reduces performance issues caused by frequent resize events.
3. Auto-saving Data
You can use debouncing for auto-saving functionality in forms or text editors to reduce frequent saves as the user types.
import React, { useState, useEffect } from "react";
import useDebounce from "./useDebounce";
const AutoSaveComponent = () => {
const [text, setText] = useState("");
const debouncedText = useDebounce(text, 1000);
useEffect(() => {
if (debouncedText) {
console.log("Auto-saving:", debouncedText);
// Perform save operation here
}
}, [debouncedText]);
return (
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Start typing..."
/>
);
};
export default AutoSaveComponent;
This prevents excessive save requests by only triggering saves after the user pauses typing.
4. Filtering Large Datasets
Debouncing is beneficial when filtering large datasets, as it reduces unnecessary re-renders and improves performance.
import React, { useState } from "react";
import useDebounce from "./useDebounce";
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
const FilterComponent = () => {
const [filter, setFilter] = useState("");
const debouncedFilter = useDebounce(filter, 400);
const filteredItems = items.filter((item) =>
item.toLowerCase().includes(debouncedFilter.toLowerCase())
);
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter items..."
/>
<p>Results: {filteredItems.length}p>
div>
);
};
export default FilterComponent;
This approach minimizes unnecessary UI re-renders by reducing the frequency of filtering.
Conclusion
In this article, we’ve demonstrated how debouncing user input can greatly improve performance in React applications. By implementing the useDebounce
custom hook, we can optimize API calls, state updates, and UI re-renders. The useDebounce
hook is versatile and can be applied in many situations, such as handling search inputs, form validations, window resize events, and filtering large datasets. Utilizing this pattern leads to a more efficient, responsive, and user-friendly experience.