Table of Contents
- Debouncing: A Strategic Delay
- Throttling: Controlling Event Frequency
- Implementing in React: Debounce and Throttle with Custom Hooks
- Final Thoughts
Performance optimization is critical in modern web applications, especially those that involve user interactions like typing in a search bar, scrolling, or resizing a window. These actions can fire off many function calls in a short time, which can degrade performance.
To mitigate this, two common techniques are debouncing and throttling, which allow you to control the rate at which a function is invoked, leading to a smoother, more efficient experience.
Debouncing: A Strategic Delay
Debouncing delays the execution of a function until a specified time has passed since the last event trigger. It is particularly helpful when dealing with events like search inputs, where you want to avoid making an API request on every keystroke.
How Debouncing Works
Imagine a search input where you want to wait until the user has stopped typing for 300ms before making an API request. Debouncing allows you to ensure that the function is only executed after the user has paused typing, preventing unnecessary API calls.
Debouncing Example
function debounce(func, delay) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
function searchAPI() {
console.log("API request made");
}
const debouncedSearch = debounce(searchAPI, 300);
debouncedSearch(); // Only triggers 300ms after the last call
Here, the API request will only be made if the user pauses for 300ms.
Throttling: Controlling Event Frequency
In contrast to debouncing, throttling ensures that a function is called at most once every specified interval, even if the event continues to trigger. This technique is ideal for scenarios like window resizing or scrolling, where the events fire continuously.
How Throttling Works
Throttling allows a function to execute only once during a defined period (e.g., 200ms), ensuring that the function is not overwhelmed by repeated triggers.
Throttling Example
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function () {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
function updateLayout() {
console.log("Layout updated");
}
const throttledUpdate = throttle(updateLayout, 200);
window.addEventListener("resize", throttledUpdate);
In this example, the layout update function will only be called once every 200ms during window resizing.
Implementing in React: Debounce and Throttle with Custom Hooks
In React, we can use custom hooks to make the debounce and throttle functionality reusable across components. This enhances modularity and optimizes performance in various interactions.
Custom Hook for Debouncing
import { useRef, useCallback } from "react";
const useDebounce = (func, delay) => {
const timer = useRef(null);
return useCallback(
(...args) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => func(...args), delay);
},
[func, delay]
);
};
export default useDebounce;
Using the Debounce Hook
import React, { useState } from "react";
import useDebounce from "./useDebounce";
const SearchComponent = () => {
const [searchTerm, setSearchTerm] = useState("");
const fetchResults = (query) => {
console.log(`Fetching results for ${query}`);
return new Promise((resolve) => setTimeout(resolve, 1000));
};
const debouncedFetch = useDebounce(fetchResults, 300);
const handleSearch = (e) => {
setSearchTerm(e.target.value);
debouncedFetch(e.target.value);
};
return <input value={searchTerm} onChange={handleSearch} placeholder="Search..." />;
};
export default SearchComponent;
Custom Hook for Throttling
import { useRef, useCallback } from "react";
const useThrottle = (func, limit) => {
const lastRun = useRef(Date.now());
return useCallback(
(...args) => {
const now = Date.now();
if (now - lastRun.current >= limit) {
func(...args);
lastRun.current = now;
}
},
[func, limit]
);
};
export default useThrottle;
Using the Throttle Hook
import React, { useEffect } from "react";
import useThrottle from "./useThrottle";
const ScrollComponent = () => {
const handleScroll = () => {
console.log("Scrolled!");
};
const throttledScroll = useThrottle(handleScroll, 500);
useEffect(() => {
window.addEventListener("scroll", throttledScroll);
return () => window.removeEventListener("scroll", throttledScroll);
}, [throttledScroll]);
return <div style={{ height: "200vh" }}>Scroll down to see the effect</div>;
};
export default ScrollComponent;
Final Thoughts
Both debouncing and throttling are indispensable techniques to enhance performance in modern applications. While debouncing is ideal for inputs like search fields, throttling is best suited for high-frequency events like scrolling. Custom hooks in React, like useDebounce
and useThrottle
, make these optimizations easy to implement across your app, ensuring a more efficient, responsive experience.
Top comments (0)