Using Throttle and Debounce in React


An overview of

Throttle and Debounce solve optimization problems.

Throttle skips function calls at certain intervals.

Throttle & Debounce scheme:

Examples of using Throttle:

1) If the user changes the size of the browser window and we need to change the content of the site.
Without optimization, the following happens. The window resize event handler is called for each window resize event. So if the user, for example, resizes the window for 10 seconds, there could be 100, 200, etc. events that we need to handle.

2) Showing the user the number of percent of page scrolling. When the user scrolls the page there are scroll events that we need to handle. With throttle we can reduce the number of page scroll events handled by the user by setting a time interval.

Examples of Debounce usage:

1) Processing user search query data.
When a user enters a search query, the search options are presented. This occurs as follows.
When the text entered by the user changes, a request is sent to the server in which we pass the characters already typed. Then we get a response from the server with possible search options and show them to the user.
Each time the user changes the text, an event handler is called in which a request is made to the server.
To optimize the number of requests sent to the server we use Debounce.
When the user changes the text, applying Debounce allows us to create a timer, for example for 1 second. If 1 second passes and the user has not changed the text a second time, the event handler is called and a request to the server is executed. If the user changes the text a second time in 1 second, the 1st timer is reset and a new timer is created again for 1 second.
Thus, if the user edits the search text quickly (faster than 1 second), the request will be sent to the server only once, after the user stops typing.

2) Sending analytics data to the server. For example, the user moves the mouse over the site, we write the coordinates of the mouse in an array, then Debounce allows you to send information about the movement of the client’s mouse to the server only after the client stops moving the mouse.

So, in this article I will show you how to use Throttle and Debounce in a React application.

Step 1 — Application Template

Create an application template with create-react-app and run it:

npx create-react-app throttle-debounce
cd throttle-debounce
npm start
Enter fullscreen mode Exit fullscreen mode

Replace the content of file App.css with your stylesheet:

body {
    display: flex;
    justify-content: center;
    width: 100%;
}
h1 {
    text-align: center;
    margin: 0.5rem 0;
}
.l-scroll {
    overflow-y: scroll;
    overflow-x: hidden;
    width: 380px;
    height: 200px;
    margin-top: 0.5rem;
}
.scroll-content {
    width: 100%;
    background-color: bisque;
    padding: 0 1rem;
}
.l-scroll::-webkit-scrollbar {
    width: 10px;
    height: 8px;
    background-color: darkturquoise;
}
.l-scroll::-webkit-scrollbar-thumb {
    background-color: blueviolet;
}
Enter fullscreen mode Exit fullscreen mode

Replace the content of the file App.js with our application template:

import './App.css';
import { useMemo } from 'react';

function App() {
    return (
        <>
            <h1>Throttle & Debounce</h1>
            <div className="l-scroll">
                <div className="scroll-content">
                    <TallContent />
                </div>
            </div>
        </>
    );
}

// Прокручиваемый контент большой высоты
function TallContent(){
    const dataElements = useMemo(() => {
        const genData = [];
        for(let i=1; i<=200; i++){
            genData.push(
                <div key={i}>Line: {i}</div>
            );
        }
        return genData;
    }, []);

    return(
        <>
            {dataElements}
        </>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The application template is ready, let’s start the second step: the normal scroll event handler.

Step 2 — The Standard Event Handler

Here we will add the normal event handler for scroll events and count the number of times the handler will be called when a user is scrolling through an item on the page.

Let’s add a state for the number of times the event handler is invoked in the App component:

// В начале файла
import { useState, useMemo } from 'react';
// Внутри компонента App
const [scrollHandleCount, setScrollHandleCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

Then let’s add a scroll event handler, by adding onScroll attribute on the h1 element with header:

// Было
<div className="l-scroll">
    ...
</div>

// Стало
<div className="l-scroll" onScroll={handleScroll}>
    ...
</div>
Enter fullscreen mode Exit fullscreen mode

Let's also add an event handling function handleScroll into the App component:

function handleScroll(){
    handleUsualScroll();
}
Enter fullscreen mode Exit fullscreen mode

Inside the handleScroll function we have placed a function which will process the normal event. Let's add this function to our App component:

function handleUsualScroll(){
    setScrollHandleCount((prevState) => {
        return ++prevState;
    });
}
Enter fullscreen mode Exit fullscreen mode

All we need to do now is to show the counter state to the user, for this we will add a line of code under h1 header:

<span>
   Usual scroll handle count: {scrollHandleCount}
</span>
<br />
Enter fullscreen mode Exit fullscreen mode

Now we should see the number of times the handleUsualScroll() function has been called.

The full code of the App component at the moment:

function App() {
    const [scrollHandleCount, setScrollHandleCount] = useState(0);
    return (
        <>
            <h1>Throttle & Debounce</h1>
            <span>
                Usual scroll handle count: {scrollHandleCount}
            </span>
            <br />
            <div className="l-scroll" onScroll={handleScroll}>
                <div className="scroll-content">
                    <TallContent />
                </div>
            </div>
        </>
    );

    function handleScroll(){
        handleUsualScroll();
    }
    function handleUsualScroll(){
        setScrollHandleCount((prevState) => {
            return ++prevState;
        });
    }    
}
Enter fullscreen mode Exit fullscreen mode

Step 3 - Throttle event handler

The Throttlethrottle event handler in our case should call the scrollThrottleHandleCount and skip the calls to increase the counter at certain intervals.
In order to realize this we need a timer which, when started, changes the state Throlle into In progress. In this case, if the state In progress, then the processing of user events (scrolling through the page item) is skipped.
As soon as the timer happens, the state Throttle will change into Not in progress, which means that our handler will process user events again. Thus, we skip user events at a given interval.
Let's implement the above:

// Добавим useRef для хранения состояния inProgress
import { useState, useRef, useMemo } from 'react';
Enter fullscreen mode Exit fullscreen mode

Then in the App component we'll add the state of the event handler's call counter with Throttle and ref for storing the inProgress state:

// Кол-во вызовов обработчика событий с Throttle
const [
   scrollThrottleHandleCount,
   setScrollThrottleHandleCount
] = useState(0);
// Храним состояние in progress
const throttleInProgress = useRef();
Enter fullscreen mode Exit fullscreen mode

Here it is important to note that throttleInProgress is part of the side effect associated with the timer, which means we will store the state in a ref object, because useRef returns an object that exists for the entire life of the component, There is no unnecessary rendering of the component when you change the current property of the object returned by useRef, as opposed to useState.
Now let's add the event handler itself with Throttle into the App component:

function handleThrottleScroll(){
    // Если состояние inProgress - выходим из функции,
    // пропускаем обработку события 
    if(throttleInProgress.current){ return; }
    // Устанавливаем inProgress в true и запускаем таймер
    throttleInProgress.current = true;
    setTimeout(() => {
        // Увеличиваем состояние throttleHandleCount
        // на единицу
        setScrollThrottleHandleCount((prevState) => {
            return ++prevState;
        });
        // inProgress устанавливаем в false,
        // значит при следующем запуске
        // setTimeout снова сработает
        throttleInProgress.current = false;
    }, 500);
}
Enter fullscreen mode Exit fullscreen mode

Two simple actions remain: add the display of the counter state from Throttle to the user and add handleThrottleScroll() to handleScroll():

// После заголовка h1
<span>
   Throttle scroll handle count: {scrollThrottleHandleCount}
</span>

// В функцию handleScroll() после handleUsualScroll();
handleThrottleScroll();
Enter fullscreen mode Exit fullscreen mode

As a result, we get:


The normal event handler called the business logic of the application 181 times, but with Throttle only 9.
The full code of the App component with Throttle:

function App() {
    const [scrollHandleCount, setScrollHandleCount] = useState(0);
    const [
        scrollThrottleHandleCount,
        setScrollThrottleHandleCount
    ] = useState(0);
    const throttleInProgress = useRef();

    return (
        <>
            <h1>Throttle & Debounce</h1>
            <span>
                Usual scroll handle count: {scrollHandleCount}
            </span>
            <br />
            <span>
                Throttle scroll handle count: {scrollThrottleHandleCount}
            </span>
            <br />
            <div className="l-scroll" onScroll={handleScroll}>
                <div className="scroll-content">
                    <TallContent />
                </div>
            </div>
        </>
    );

    function handleScroll(){
        handleUsualScroll();
        handleThrottleScroll();
    }
    function handleUsualScroll(){
        setScrollHandleCount((prevState) => {
            return ++prevState;
        });
    }
    function handleThrottleScroll(){
        if(throttleInProgress.current){ return; }
        throttleInProgress.current = true;
        setTimeout(() => {
            setScrollThrottleHandleCount((prevState) => {
                return ++prevState;
            });
            throttleInProgress.current = false;
        }, 500);
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's move on to the final step, and implement an Debounce event handler.

Step 4 - The Debounce event handler

The Debounce in this example delays the increase of the scrollDebounceHandleCount counter until a certain amount of time has passed since the last call to the event handler.
Let's add the state of the number of times the event handler is called with Debounce, ref to store the timer ID in the App component:

const [
    scrollDebounceHandleCount,
    setScrollDebounceHandleCount
] = useState(0);
const timerDebounceRef = useRef();
Enter fullscreen mode Exit fullscreen mode

Then we show scrollDebounceHandleCount to the user and add our handleDebounceScroll() method to handleScroll():

// После h1
<span>
    Debound scroll handle count: {scrollDebounceHandleCount}
</span>
// В функцию handleScroll()
handleDebounceScroll();
Enter fullscreen mode Exit fullscreen mode

Now we just have to write the function itself handleDebounceScroll:

function handleDebounceScroll(){
    // Если ID таймена установлено - сбрасываем таймер
    if(timerDebounceRef.current){
        clearTimeout(timerDebounceRef.current);
    }
    // Запускаем таймер, возвращаемое ID таймера
    // записываем в timerDebounceRef
    timerDebounceRef.current = setTimeout(() => {
        // Вызываем увеличение счётчика кол-ва
        // выполнения бизнес логики приложения с Debounce
        setScrollDebounceHandleCount((prevState) => {
            return ++prevState;
        });
    }, 500);
}
Enter fullscreen mode Exit fullscreen mode

The result is that the Debounce counter is incremented only when the user stops scrolling for more than or equal to 500 milliseconds:


Full text App component:

function App() {
    const [scrollHandleCount, setScrollHandleCount] = useState(0);
    const [
        scrollThrottleHandleCount,
        setScrollThrottleHandleCount
    ] = useState(0);
    const [
        scrollDebounceHandleCount,
        setScrollDebounceHandleCount
    ] = useState(0);

    const throttleInProgress = useRef();
    const timerDebounceRef = useRef();

    return (
        <>
            <h1>Throttle & Debounce</h1>
            <span>
                Usual scroll handle count: {scrollHandleCount}
            </span>
            <br />
            <span>
                Throttle scroll handle count: {scrollThrottleHandleCount}
            </span>
            <br />
            <span>
                Debound scroll handle count: {scrollDebounceHandleCount}
            </span>
            <div className="l-scroll" onScroll={handleScroll}>
                <div className="scroll-content">
                    <TallContent />
                </div>
            </div>
        </>
    );

    function handleScroll(){
        handleUsualScroll();
        handleThrottleScroll();
        handleDebounceScroll();
    }
    function handleUsualScroll(){
        setScrollHandleCount((prevState) => {
            return ++prevState;
        });
    }
    function handleThrottleScroll(){
        if(throttleInProgress.current){ return; }
        throttleInProgress.current = true;
        setTimeout(() => {
            setScrollThrottleHandleCount((prevState) => {
                return ++prevState;
            });
            throttleInProgress.current = false;
        }, 500);
    }
    function handleDebounceScroll(){
        if(timerDebounceRef.current){
            clearTimeout(timerDebounceRef.current);
        }
        timerDebounceRef.current = setTimeout(() => {
            setScrollDebounceHandleCount((prevState) => {
                return ++prevState;
            });
        }, 500);
    }
}
Enter fullscreen mode Exit fullscreen mode

Subscribe to the blog, like it, like it and add it to bookmarks.
Do not forget about the unicorns.

Thank you for your attention.

Оцените статью
Procodings.ru
Добавить комментарий