Published on 15.7.2019
One of the most common features in modern web applications is real-time search. Whether you’re building a to-do list, a product catalog, or a social media app, users expect to see results update as they type. React’s useState
and useEffect
hooks are perfect for implementing this feature. However, to ensure performance remains optimal, especially when dealing with large datasets, you may need to use debouncing to limit the number of API calls or state updates.
In this post, we’ll walk through how to implement a real-time search feature with live filtering using useEffect
and useState
. We’ll also cover how to debounce the input to prevent unnecessary processing.
Setting Up the Real-Time Search
Before diving into the code, let’s briefly discuss the scenario. Imagine you have a list of items, and you want the user to filter through that list in real time as they type into an input field. The challenge here is to manage both the input state and the filtered results efficiently.
Let’s start by setting up the basic components and state for the search feature.
import React, { useState, useEffect } from 'react';
const items = [
'Apple',
'Banana',
'Cherry',
'Date',
'Grapes',
'Kiwi',
'Lemon',
'Mango',
'Orange',
'Peach',
];
function RealTimeSearch() {
const [query, setQuery] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
useEffect(() => {
setFilteredItems(
items.filter((item) => item.toLowerCase().includes(query.toLowerCase()))
);
}, [query]);
const handleChange = (event) => {
setQuery(event.target.value);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Search for fruits..."
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default RealTimeSearch;
How This Works:
- We create a list of items (
items
) for the search to filter through. - We use
useState
to store the current query (query
) entered by the user and the filtered list (filteredItems
). - The
useEffect
hook listens for changes in thequery
state. When the query changes, it filters the list of items and updates thefilteredItems
state with the results. - The input field updates the
query
state whenever the user types, triggering theuseEffect
hook and updating the list in real-time.
Debouncing the Input
While the above implementation works fine, it can lead to performance issues when the list becomes large or when the user types quickly. Each keystroke causes a re-render, and in the case of API calls, it might lead to a flood of requests.
To improve this, we can implement debouncing. Debouncing allows us to delay the update until the user stops typing for a specified amount of time. This reduces the number of updates and ensures better performance.
Let’s add a debounce function to our search:
import React, { useState, useEffect } from 'react';
// Debounce function
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Clean up the timeout on value change
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
const items = [
'Apple',
'Banana',
'Cherry',
'Date',
'Grapes',
'Kiwi',
'Lemon',
'Mango',
'Orange',
'Peach',
];
function RealTimeSearch() {
const [query, setQuery] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
// Apply debounce to the query
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
setFilteredItems(
items.filter((item) => item.toLowerCase().includes(debouncedQuery.toLowerCase()))
);
}, [debouncedQuery]);
const handleChange = (event) => {
setQuery(event.target.value);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Search for fruits..."
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default RealTimeSearch;
How Debouncing Works:
- Custom
useDebounce
Hook:- This hook takes a value (
query
) and a delay (500ms
in our case) and returns the debounced value. - It uses
setTimeout
to wait for the specified delay before updating the debounced value. If the user types again within that time, the previous timeout is cleared and reset. - This ensures that the search query is only updated after the user stops typing for the given period.
- This hook takes a value (
- Using Debounced Query:
- We now use
debouncedQuery
inside theuseEffect
hook instead ofquery
. The filtering only occurs when the debounced query has settled, improving performance by reducing the number of re-renders and filter operations.
- We now use
Why Use Debouncing?
Debouncing is especially useful for optimizing performance when:
- Making API calls based on user input (to avoid sending a request for every keystroke).
- Filtering large datasets or performing computationally expensive operations.
- Improving the overall user experience by providing smooth interactions.
In the example above, debouncing the search input prevents unnecessary filtering as the user types quickly, ensuring the UI remains responsive.
Conclusion
In this post, we’ve built a real-time search feature using React’s useState
and useEffect
hooks. We also optimized the performance by introducing debouncing with a custom useDebounce
hook. Debouncing ensures that we don’t overwhelm the application with too many updates or requests, making the search smoother and more efficient.
By leveraging hooks like useState
and useEffect
along with debouncing, you can create powerful and responsive real-time search experiences in your React applications.
Happy coding!