An Interest In:
Web News this Week
- March 20, 2024
- March 19, 2024
- March 18, 2024
- March 17, 2024
- March 16, 2024
- March 15, 2024
- March 14, 2024
How to use debouncing to Improve the performance of the search functionality
In this article, we will see how to use debouncing to improve the performance of search functionality in the application.
If we're making an API call to the server for every character typed in the input search box and the data returned by API contains a lot of data let's say 500 or 1000 user records then it will slow down your application.
Because for every character typed in the search box, we're making an API call and the server may take some time to return data and before the server returns the data we're making another API call for the next character typed.
In almost every application we need to add some form of search functionality may be to filter some records or get the result from API.
So this is a common scenario and making an API call for every character typed may be expensive because some third-party applications or cloud providers like firebase, AWS, etc provide only a limited number of API requests, and cost is incurred for every additional request.
So to handle this scenario, we can use the debouncing functionality.
Let's first understand what is debouncing.
Debouncing allows us to call afunction after a certain amount of time has passed. This is very useful to avoidunnecessary API calls to the server If you're making an API call for every character typed in the input text.
Let's understand this by writing some code.
Without Debouncing in class component
import React from 'react';import axios from 'axios';import { Form } from 'react-bootstrap';export default class WithoutDebouncingClass extends React.Component { state = { input: '', result: [], errorMsg: '', isLoading: false }; handleInputChange = (event) => { const input = event.target.value; this.setState({ input, isLoading: true }); axios .get(`https://www.reddit.com/search.json?q=${input}`) .then((result) => { this.setState({ result: result.data.data.children, errorMsg: '', isLoading: false }); }) .catch(() => { this.setState({ errorMsg: 'Something went wrong. Try again later.', isLoading: false }); }); }; render() { const { input, result, errorMsg, isLoading } = this.state; return ( <div className="container"> <div className="search-section"> <h1>Debouncing Demo</h1> <Form> <Form.Group controlId="search"> <Form.Control type="search" placeholder="Enter text to search" onChange={this.handleInputChange} value={input} autoComplete="off" /> </Form.Group> {errorMsg && <p>{errorMsg}</p>} {isLoading && <p className="loading">Loading...</p>} <ul className="search-result"> {result.map((item, index) => ( <li key={index}>{item.data.title}</li> ))} </ul> </Form> </div> </div> ); }}
In the above code, we're displaying a search box where user types some value and we're calling the handleInputChange
method on the onChange
event of the input text box.
Inside that method, we're making an API call to reddit
by passing the search string and we're storing the result in the results
array in the state and displaying the result as an unordered list.
As you can see, on every character typed, we are making an API call. So we are unnecessarily increasing the server API calls.
If the server is taking more time to return the data, you might see the previous result even when you are expecting new results based on your input value.
To fix this, we can use debouncing where we only make an API request after half-second(500 milliseconds) once a user has stopped typing which is more beneficial. It will save from unnecessary requests and will also save from previous API call result being displayed for a short time.
With debouncing in class component
Here, we will use the debounce
method provided by lodash
library to add the debouncing functionality.
import React from 'react';import axios from 'axios';import _ from 'lodash';import { Form } from 'react-bootstrap';export default class WithDebouncingClass extends React.Component { constructor(props) { super(props); this.state = { input: '', result: [], errorMsg: '', isLoading: false }; this.handleSearchText = _.debounce(this.onSearchText, 500); } onSearchText = (input) => { this.setState({ isLoading: true }); axios .get(`https://www.reddit.com/search.json?q=${input}`) .then((result) => { this.setState({ result: result.data.data.children, errorMsg: '', isLoading: false }); }) .catch(() => { this.setState({ errorMsg: 'Something went wrong. Try again later.', isLoading: false }); }); }; handleInputChange = (event) => { const input = event.target.value; this.setState({ input }); this.handleSearchText(input); }; render() { const { input, result, errorMsg, isLoading } = this.state; return ( <div className="container"> <div className="search-section"> <h1>Debouncing Demo</h1> <Form> <Form.Group controlId="search"> <Form.Control type="search" placeholder="Enter text to search" onChange={this.handleInputChange} value={input} autoComplete="off" /> </Form.Group> {errorMsg && <p>{errorMsg}</p>} {isLoading && <p className="loading">Loading...</p>} <ul className="search-result"> {result.map((item, index) => ( <li key={index}>{item.data.title}</li> ))} </ul> </Form> </div> </div> ); }}
The lodash's debounce
method accepts two parameters.
- A function to execute
- The number of milliseconds to wait before executing the passed function
this.handleSearchText = _.debounce(this.onSearchText, 500);
The debounce
method returns a function which we stored in this.handleSearchText
class variable and we're calling it in handleInputChange
handler which gets called when the user types something in the input search textbox.
When we call the handleSearchText
method, it internally calls the onSearchText
method where we're making an API call to reddit.
Note that, we're calling the debounce
function inside the constructor because this initialization needs to be done only once.
As you can see, with the added debouncing functionality, the API call is only made after half-second(500 milliseconds) when we stopped typing, thereby reducing the number of API calls and also the result is not flickered and we're getting only the final result which is expected and useful behavior.
If you're using React hooks, then you can't use the same code as a class to implement the debouncing. So let's see how to add debouncing in code using hooks.
Without Debouncing in React hooks
Let's first write the code without debouncing using hooks
import React, { useState } from 'react';import axios from 'axios';import { Form } from 'react-bootstrap';const WithoutDebouncingHooks = () => { const [input, setInput] = useState(''); const [result, setResult] = useState([]); const [errorMsg, setErrorMsg] = useState(''); const [isLoading, setIsLoading] = useState(false); const handleInputChange = (event) => { const input = event.target.value; setInput(input); setIsLoading(true); axios .get(`https://www.reddit.com/search.json?q=${input}`) .then((result) => { setResult(result.data.data.children); setErrorMsg(''); setIsLoading(false); }) .catch(() => { setErrorMsg('Something went wrong. Try again later.'); setIsLoading(false); }); }; return ( <div className="container"> <div className="search-section"> <h1>Debouncing Demo</h1> <Form> <Form.Group controlId="search"> <Form.Control type="search" placeholder="Enter text to search" onChange={handleInputChange} value={input} autoComplete="off" /> </Form.Group> {errorMsg && <p>{errorMsg}</p>} {isLoading && <p className="loading">Loading...</p>} <ul className="search-result"> {result.map((item, index) => ( <li key={index}>{item.data.title}</li> ))} </ul> </Form> </div> </div> );};export default WithoutDebouncingHooks;
This is the same code of debouncing without class written using hooks.
Let's see how we can add debouncing to this code.
With debouncing in React hooks
import React, { useState, useEffect, useRef } from 'react';import axios from 'axios';import _ from 'lodash';import { Form } from 'react-bootstrap';const WithDebouncingHooks = () => { const [input, setInput] = useState(''); const [result, setResult] = useState([]); const [errorMsg, setErrorMsg] = useState(''); const [isLoading, setIsLoading] = useState(false); const inputRef = useRef(); useEffect(() => { // initialize debounce function to search once user has stopped typing every half second inputRef.current = _.debounce(onSearchText, 500); }, []); const onSearchText = (input) => { setIsLoading(true); axios .get(`https://www.reddit.com/search.json?q=${input}`) .then((result) => { setResult(result.data.data.children); setErrorMsg(''); setIsLoading(false); }) .catch(() => { setErrorMsg('Something went wrong. Try again later.'); setIsLoading(false); }); }; const handleInputChange = (event) => { const input = event.target.value; setInput(input); inputRef.current(input); }; return ( <div className="container"> <div className="search-section"> <h1>Debouncing Demo</h1> <Form> <Form.Group controlId="search"> <Form.Control type="search" placeholder="Enter text to search" onChange={handleInputChange} value={input} autoComplete="off" /> </Form.Group> {errorMsg && <p>{errorMsg}</p>} {isLoading && <p className="loading">Loading...</p>} <ul className="search-result"> {result.map((item, index) => ( <li key={index}>{item.data.title}</li> ))} </ul> </Form> </div> </div> );};export default WithDebouncingHooks;
In this code, we're calling the debounce
function inside the useEffect
hook by passing an empty array []
as a second argument because this code needs to be executed only once.
And we're storing the result of the function in inputRef.current
. inputRef
is a ref
created by calling useRef()
hook. It contains a current
property which we can use to retain the value even after the component is re-rendered.
Using the local variable to store the result of debounce
function will not work because for every re-render of the component previous variables will get lost. So React provided a ref way of persisting data across re-render inside the components using Hooks.
And then inside the handleInputChange
handler, we're calling the function stored inside the inputRef.current
variable.
const handleInputChange = (event) => { const input = event.target.value; setInput(input); inputRef.current(input);};
If you run the application now, you will see the expected behavior similar to debouncing inside class.
That's it about this article. I hope you enjoyed the article and found it useful.
You can find the complete source code for this application in this repository and live demo at this url
Don't forget to subscribe to get my weekly newsletter with amazing tips, tricks and articles directly in your inbox here.
Original Link: https://dev.to/myogeshchavan97/using-debouncing-to-improve-the-performance-of-your-application-s-search-functionality-31j7
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To