skip to Main Content

Hello everyone im trying to prevent double click but im not doing any API calls, and dont want to use timeout

I did few approachs

first one

import React, { useState } from "react";
const App = () => {
  const [disabled, setDisabled] = useState(false);

  const handleClick = () => {
    return new Promise((resolve) => {
      for (let i = 0; i < 99999999999; i++) {}
      resolve(false);
    });
  };

  return (
    <div>
      <button
        disabled={disabled}
        onClick={() => {
          setDisabled(true);
          handleClick().then((res) => {
            setDisabled(res);
          });
        }}
      >
        {disabled ? "Disabled" : "Enable"}
      </button>
    </div>
  );
};

export default App;

this approach didnt change my button to disabled and it blocked my main thread so its bad.

Second and third approachs are look like same but its not

I know click event order is (onMouseDown -> onMouseUp -> onClick) so i tried to put my setDisabled to both onMouseDown and onMouseUp

import React, { useState } from "react";
const App = () => {
  const [disabled, setDisabled] = useState(false);

  const handleClick = () => {
    return new Promise((resolve) => {
      for (let i = 0; i < 9999; i++) {}
      resolve(false);
    });
  };

  return (
    <div>
      <button
        disabled={disabled}
        onClick={() => {
          console.log("onClick");
          handleClick().then((res) => {
            setDisabled(res);
          });
        }}
        onMouseDown={() => {
          console.log("mouseDown");
          setDisabled(true);
        }}
      >
        {disabled ? "Disabled" : "Enable"}
      </button>
    </div>
  );
};

export default App;

But since my button is disabled onClick event not fired.

I can use debounce/setTimeout etc. but i dont want to use any hard-coded timeout values.

Is there any other way to make this?

To sum up;

I want my button disabled until my long for loop is finished after that again my button is enable

3

Answers


  1. Chosen as BEST ANSWER

    With the help of @SergeySosunov

    The button was not changing to a disabled state because the UI thread was blocked by a CPU-intensive operation fired just after setDisabled call itself. This blocking prevents React from re-rendering with the new state. To resolve this, a timeout before the CPU operation was added, allowing React and the UI to have some time for re-rendering.

    const App = () => {
      const [disabled, setDisabled] = useState(false);
    
      const handleClick = () => {
        setDisabled(true);
    
        return new Promise((resolve) => {
          setTimeout(() => {
            for (let i = 0; i < 9999999999; i++) {}
            resolve(false);
          }, 0);
        });
      };
    
      return (
        <div>
          <button
            disabled={disabled}
            onClick={() => {
              handleClick().then((res) => {
                setDisabled(res);
              });
            }}
          >
            {disabled ? "Disabled" : "Enable"}
          </button>
        </div>
      );
    };
    
    export default App;
    
    

  2. You could keep a state of { lastClick: number, disabled: boolean }, only toggling disabled if the current click time exceeds last click by X seconds –

    function App() {
      const [state, setState] = React.useState({
        lastClick: 0,
        disabled: false,
      })
      return <button
        children={state.disabled ? "disabled" : "enabled"}
        onClick={e => {
          const now = Date.now()
          setState({
            lastClick: now,
            disabled: (now - state.lastClick > 1000) // 1000 ms threshold
              ? !state.disabled
              : state.disabled
          })
        }}
        type="button"
      />
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    Of course it’s a burden to tangle that logic with your component however. I would recommend a higher-order event handler like usePreventDoubleClick

    function usePreventDoubleClick(handler, timeout = 1000) {
      const last = React.useRef(Date.now())
      return {
        onClick: e => {
          const now = Date.now()
          if ((now - last.current) > timeout) {
            last.current = now
            handler(e)
          }
        }
      }
    }   
    
    function App() {
      const [disabled, setDisabled] = React.useState(false)
      return <button
        type="button"
        children={disabled ? "disabled" : "enabled"}
        {...usePreventDoubleClick(e => setDisabled(d => !d))}
      />
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
  3. Why do not You use backdropLoaders:-

    You can create a React Mui Backdrop loader and when you once click on the button just set backdrop state to true and after your long press set it to the false state

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search