blog post

Building a Debounced Input Component in React with TypeScript

Introduction

Let's explore a web application that fetches data from an API based on the user's input. Everytime user enters a character in a search input - a request is sent to get filtered data from the API. If we implement an application this way we'll experience a common performance issue related to rapid state updates.

This is where debouncing comes into play. Debouncing is a programming practice used to ensure that time-consuming operations are not executed too often. A debounced input delays the processing of the input's keyup event until the user has stopped typing for a given amount of time (often milliseconds or seconds). If the user starts typing again before this time is up, the timer resets. This approach prevents the application from making excessive calls to APIs or performing redundant operations, thus enhancing performance, UI responsiveness and overall user experience.

Implementing Debounced Input in React with TypeScript

Let's create a simple debounced input component in React using TypeScript. We'll use use-debounce NPM package for the debounce function:

tsx
import React, { useState, useEffect } from "react"; import { useDebounce } from "use-debounce"; type DebouncedInputProps = { value: string; onChange: (value: string) => void; placeholder?: string; delay?: number; // Delay in milliseconds } export const DebouncedInput: React.FC<DebouncedInputProps> = ({ value, onChange, placeholder = '', delay = 250, }) => { const [inputValue, setInputValue] = useState(value); // Create a debounced value const [debouncedValue] = useDebounce(inputValue, delay); /** * Call the onChange function when a user stops typing */ useEffect(() => { onChange(debouncedValue); }, [debouncedValue]); return ( <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder={placeholder} /> ); };

Here an input value in the component's state is updated everytime a user enters a character. But DebouncedInput component calls the onChange function only when the user stops typing for more than a given delay.

onChange function reports a debounced value changes to the parent component. Let's create a page that utilizes the DebouncedInput component to fetch products bases on a user input. First, we need to define some products that will be fetched:

tsx
type Product = { id: number, title: string } const products: Product[] = [ { id: 1, title: "Samsung Galaxy S24" }, { id: 2, title: "Samsung Galaxy Fold 4" }, { id: 3, title: "iPhone 14 pro" }, { id: 4, title: "One Plus 7" }, { id: 5, title: "Xiaomi 14 Pro" } ];

Now let's create a SearchPage component.

tsx
export const SearchPage: React.FC = () => { const [filteredProducts, setFilteredProducts] = useState<Product[]>(products); return ( <div> <h1>Debounced Search Example</h1> <DebouncedInput value={''} onChange={searchProductsAsync} placeholder="Type to search..." delay={250} // Debounce delay in milliseconds /> <div> {filteredProducts.length > 0 ? ( <div> {filteredProducts.map(product => ( <div key={product.id}>{product.title}</div> ))} </div> ) : ( <p>No products found.</p> )} </div> </div> ); };

Here we render a DebouncedInput where the user enters a name of the product to search for. Then we need to call an API to get filtered products.

Here is a function callback for a DebouncedInput that fetches products when a user stops typing for 250ms:

tsx
const searchProductsAsync = async (searchText: string) => { // Simulating a network request with a delay await new Promise(resolve => setTimeout(resolve, 250)); if (!searchText.trim()) { setFilteredProducts(products); return; } const lowercasedFilter = searchText.toLowerCase(); const filteredData = products.filter(p => p.title.toLowerCase().includes(lowercasedFilter) ); setFilteredProducts(filteredData); };

This implementation with debounced input prevents our API from being called too often, making the web page more performant and UI more responsive.

Summary

By implementing a debounced input in React, you can significantly enhance the user experience in your applications, especially in scenarios where inputs trigger time-consuming operations like API calls. This example demonstrates the power of debouncing in managing efficient state updates and provides a reusable DebouncedInput component that can be easily integrated into any React project. A debounced input is easy to implement and it not only improves performance but also make user interface more responsive, making it an essential technique in the modern web developer's toolkit.

Hope you find this blog post useful. Happy coding!

Improve Your .NET and Architecture Skills

Join my community of 1000+ developers and architects.

Each week you will get 2 practical tips with best practises and architecture advice.