Account imageLoginSign UpAccount image
Loading votes....
Save Question

How to debounce a search input in React to avoid excessive API calls?

clock icon

asked 3 months ago

Message icon

1

Eye icon

24

I have a search input field that makes an API call to fetch results on every onChange event. This causes too many requests as the user types (e.g., on every keystroke). How can I delay the API call until the user stops typing for, say, 300ms? I'm using functional components and hooks.

Here's my current code (simplified):

jsx

1function Search() {
2 const [query, setQuery] = useState('');
3 const [results, setResults] = useState([]);
4
5 const handleChange = (e) => {
6 setQuery(e.target.value);
7 // This runs on every keystroke:
8 fetchResults(e.target.value).then(setResults);
9 };
10
11 return (
12 <input
13 type="text"
14 value={query}
15 onChange={handleChange}
16 placeholder="Search..."
17 />
18 );
19}
1function Search() {
2 const [query, setQuery] = useState('');
3 const [results, setResults] = useState([]);
4
5 const handleChange = (e) => {
6 setQuery(e.target.value);
7 // This runs on every keystroke:
8 fetchResults(e.target.value).then(setResults);
9 };
10
11 return (
12 <input
13 type="text"
14 value={query}
15 onChange={handleChange}
16 placeholder="Search..."
17 />
18 );
19}

What's the proper way to implement debouncing in React without breaking the component lifecycle?

1 Answer

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.

1

Write your answer here

Top Questions