Debouncing a Search Input in React
To debounce a search input in React and avoid excessive API calls, you can use the useCallback and useEffect hooks along with the setTimeout function.
Problem with the Current Code
The current code makes an API call on every keystroke, which can lead to a large number of requests. This can be optimized by debouncing the input, i.e., delaying the API call until the user stops typing for a certain amount of time.
Solution
You can use the useCallback hook to memoize the handleChange function and the useEffect hook to debounce the API call. Here's an example of how you can implement debouncing:
1import { useState, useCallback, useEffect } from 'react';
2
3function Search() {
4 const [query, setQuery] = useState('');
5 const [results, setResults] = useState([]);
6 const [timeoutId, setTimeoutId] = useState(null);
7
8 const fetchResults = async (query) => {
9 // Simulate an API call
10 const response = await new Promise((resolve) => {
11 setTimeout(() => {
12 resolve(['Result 1', 'Result 2', 'Result 3']);
13 }, 1000);
14 });
15 return response;
16 };
17
18 const handleChange = useCallback((e) => {
19 setQuery(e.target.value);
20 }, []);
21
22 useEffect(() => {
23 if (timeoutId) {
24 clearTimeout(timeoutId);
25 }
26 if (query) {
27 const id = setTimeout(async () => {
28 const results = await fetchResults(query);
29 setResults(results);
30 }, 300);
31 setTimeoutId(id);
32 }
33 }, [query, timeoutId]);
34
35 return (
36 <input
37 type="text"
38 value={query}
39 onChange={handleChange}
40 placeholder="Search..."
41 />
42 );
43}
1import { useState, useCallback, useEffect } from 'react';
2
3function Search() {
4 const [query, setQuery] = useState('');
5 const [results, setResults] = useState([]);
6 const [timeoutId, setTimeoutId] = useState(null);
7
8 const fetchResults = async (query) => {
9 // Simulate an API call
10 const response = await new Promise((resolve) => {
11 setTimeout(() => {
12 resolve(['Result 1', 'Result 2', 'Result 3']);
13 }, 1000);
14 });
15 return response;
16 };
17
18 const handleChange = useCallback((e) => {
19 setQuery(e.target.value);
20 }, []);
21
22 useEffect(() => {
23 if (timeoutId) {
24 clearTimeout(timeoutId);
25 }
26 if (query) {
27 const id = setTimeout(async () => {
28 const results = await fetchResults(query);
29 setResults(results);
30 }, 300);
31 setTimeoutId(id);
32 }
33 }, [query, timeoutId]);
34
35 return (
36 <input
37 type="text"
38 value={query}
39 onChange={handleChange}
40 placeholder="Search..."
41 />
42 );
43}
Explanation
- We use the
useCallback hook to memoize the handleChange function so that it's not recreated on every render.
- We use the
useEffect hook to debounce the API call. When the query changes, we clear any existing timeout and set a new one with a delay of 300ms.
- When the timeout expires, we make the API call and update the
results state.
Advice
- Make sure to clear any existing timeout when the component unmounts to prevent memory leaks.
- You can adjust the delay time according to your needs.
- Consider using a library like Lodash to handle debouncing, as it provides a more robust and efficient implementation.
Using Lodash
If you prefer to use a library to handle debouncing, you can use Lodash's debounce function. Here's an example:
1import _ from 'lodash';
2import { useState } from 'react';
3
4function Search() {
5 const [query, setQuery] = useState('');
6 const [results, setResults] = useState([]);
7
8 const fetchResults = async (query) => {
9 // Simulate an API call
10 const response = await new Promise((resolve) => {
11 setTimeout(() => {
12 resolve(['Result 1', 'Result 2', 'Result 3']);
13 }, 1000);
14 });
15 return response;
16 };
17
18 const debouncedFetchResults = _.debounce(async (query) => {
19 const results = await fetchResults(query);
20 setResults(results);
21 }, 300);
22
23 const handleChange = (e) => {
24 setQuery(e.target.value);
25 debouncedFetchResults(e.target.value);
26 };
27
28 return (
29 <input
30 type="text"
31 value={query}
32 onChange={handleChange}
33 placeholder="Search..."
34 />
35 );
36}
1import _ from 'lodash';
2import { useState } from 'react';
3
4function Search() {
5 const [query, setQuery] = useState('');
6 const [results, setResults] = useState([]);
7
8 const fetchResults = async (query) => {
9 // Simulate an API call
10 const response = await new Promise((resolve) => {
11 setTimeout(() => {
12 resolve(['Result 1', 'Result 2', 'Result 3']);
13 }, 1000);
14 });
15 return response;
16 };
17
18 const debouncedFetchResults = _.debounce(async (query) => {
19 const results = await fetchResults(query);
20 setResults(results);
21 }, 300);
22
23 const handleChange = (e) => {
24 setQuery(e.target.value);
25 debouncedFetchResults(e.target.value);
26 };
27
28 return (
29 <input
30 type="text"
31 value={query}
32 onChange={handleChange}
33 placeholder="Search..."
34 />
35 );
36}
Explanation
- We import Lodash and use its
debounce function to create a debounced version of the fetchResults function.
- We use the debounced function in the
handleChange function to make the API call with a delay of 300ms.