-1

I have a button that increments a counter with useState hook when users click on it, but I want to know if there is a way to delay the state updating for 0.5 seconds when users click on the button really fast, then update the counter at once. For example, when users click on the button 1 times each second, the counter will be updated immediately. But if users click more than 3 times in one second, the state will not be updated immediately, and the counter will only be updated when users stop clicking fast. The counter will be updated to the number of clicks during the delay. I tried to use setTimeOut but it did not work. Is there a hook for this?

function App() {
   // State to store count value
   const [count, setCount] = useState(0);


   
   // Function to increment count by 1
   const incrementCount = () => {
     // Update state with incremented value
     setCount((prev)=>{
      return prev+1
     });
   };
   return (
     <div className="app">
       <button onClick={incrementCount}>Click Here</button>
       {count}
     </div>
   );
}
Mattmmmmm
  • 155
  • 1
  • 6

3 Answers3

4

You need to apply Javascript Throttle function. Debounce is not an ideal solution here because with Debounce even after the first click user will have to wait for some time(delay) before execution happens. What you want is that on the first click counter should be incremented but after that if user clicks too fast it should not happen untill some delay ,that what Throttle function provides.

Also Thing to note that to use Throttle or Debounce in React application you will need an additional hook i.e. useCallback, which will not redfeine the function on every re-render and gives a memoized function.

More on difference between Throttle and Debounce :https://stackoverflow.com/questions/25991367/difference-between-throttling-and-debouncing-a-function#:~:text=Throttle%3A%20the%20original%20function%20will,function%20after%20a%20specified%20period.

Let's look at the code :

import { useState, useCallback } from "react";

function App() {
  // State to store count value
  const [count, setCount] = useState(0);

  // Throttle Function
  const throttle = (func, limit = 1000) => {
    let inThrottle = null;
    return (...args) => {
      if (!inThrottle) {
        func.apply(this, args);
        inThrottle = true;
        setTimeout(() => (inThrottle = false), limit);
      }
    };
  };

  // Function to increment count by 1
  const incrementCount = useCallback(
    throttle(() => {
      // Update state with incremented value
      setCount((prev) => {
        return prev + 1;
      });
    }, 1000),
    []
  );

  return (
    <div className="app">
      <button onClick={incrementCount}>Click Here</button>
      {count}
    </div>
  );
}

export default App;
lokeshj
  • 888
  • 6
  • 11
  • ```throttle ``` is a function that takes the parent's arguments as its argument right? Then why can't we simply omit the arguments since they are in the same closure? Thank you – Mattmmmmm Aug 25 '22 at 08:37
  • @lokeshj The code doesn't apply throttling, https://codesandbox.io/s/cold-river-llvbkg – Mina Aug 25 '22 at 08:54
  • @Mina I updated my code, basically we need to use useCallback hook to effectively use Throttle in react app. Check this out : https://codesandbox.io/s/withered-monad-h8y5uh?file=/src/App.js:0-896 – lokeshj Aug 27 '22 at 17:18
1

This is pure adhoc implementation. I just tried with two state variable and simple implementation. Basically,

  • firstly at initial click I'm doing count variable 1 instantly. Then, after each click, it will take 1 second for updating count state.
  • Then, I put a if block in setTimeout() method which is, if the difference between current count value and previous count value is 1, then the count variable will update. The checking is because, on each click the count variable increasing very fast. So, the condition becomes obstacle for that.

import { useState } from "react";

function App() {
  // State to store count value
  const [count, setCount] = useState(0);
  const [prevCount, setPrevCount] = useState(0);

  // Function to increment count by 1
  const incrementCount = () => {

      setPrevCount(count);
      if(count === 0) setCount(1);
      setTimeout(() => {        
        if(count - prevCount === 1) {
          setCount(prev => prev + 1);
        }
      }, 1000);
      
      
  };

  return (
    <div className="app">
      <button onClick={incrementCount}>Click Here</button>
      {count}
    </div>
  );
}

export default App;
  • Today I came to know about **debounce handling technique**. The technique refers almost the same concept. But the concept related with callback which I don't. You can use google with the title I mentioned. – Md Sajjad Hosen Noyon Jun 30 '23 at 13:52
0

This is my another answer for this problem which is debounce technique.

Debounce is a technique which waits certain period of time for invoking function again.

In the below, you can see how debounce implemented in javaScript.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="src/style.css">
  </head>
  <body>
    <button id="button">click here</button>
    <div>
      <h1 id="countValue">count: 0<h1>
    <div>

    <script src="src/script.js"></script>
  </body>
</html>
const btn = document.getElementById('button');
const cnt = document.getElementById('countValue');
let count = 0;

const debounce = (fn, delay) => {
  let timeoutId;

  return function (...args) {
    clearInterval(timeoutId);
    timeoutId = setTimeout(() => {
      fn(...args);
    }, delay);
  }
}

const doCount = () => {
  console.log(`count: ${count}`);
  count = count + 1;
  cnt.innerHTML = `count: ${count}`;
}

btn.addEventListener("click", debounce(doCount, 500));