1

I'm implementing an input number using React. This input number has a + and - button that increases or decreases the value inside an input. enter image description here

The onClick methods works fine, however, I wonder, what's the method to keep triggering the increase or decrease methods while the button keeps pressed.

Here are my methods to update the state:

  const increaseValue = () => {
    const val = isNaN(tempValue) || tempValue === '' ? 0 : Number(tempValue)
    setTempValue(val + 1)
  }

  const decreaseValue = () => {
    const val = isNaN(tempValue) || tempValue === '' ? 0 : Number(tempValue)
    setTempValue(val - 1)
  }

and here's the component

    <div className="flex flex-row h-8 w-20 rounded-lg relative bg-transparent">
      <button
        className=" bg-action text-action-100 hover:text-gray-300 hover:bg-action-300 h-full w-20 rounded-l cursor-pointer outline-none active:bg-action-200"
        onClick={decreaseValue}
      >
        <span className="m-auto text-md font-thin">−</span>
      </button>
      <input
        className="outline-none focus:outline-none text-center w-full bg-action font-thin text-xs antialiased flex items-center text-white"
        name="custom-input-number"
        value={tempValue}
        onChange={e => {
          setTempValue(e.target.value)
          type === 'number' && setIsEditing(true)
        }}
      ></input>
      <button
        className="bg-action text-action-100 hover:text-gray-300 hover:bg-action-300 h-full w-20 rounded-r cursor-pointer active:bg-action-200"
        onClick={increaseValue}
      >
        <span className="m-auto text-md font-thin">+</span>
      </button>
    </div>

Now, I want to add the feature to keep increasing/decreasing while the button is still pressed.

Dan
  • 1,518
  • 5
  • 20
  • 48
  • Does this answer your question? [Click and Hold event listener with Javascript](https://stackoverflow.com/questions/40573922/click-and-hold-event-listener-with-javascript) – BadPiggie Aug 16 '22 at 16:37
  • I'm not sure whether using setInterval is right because it's currently throwing an exception: Can't perform a React state update on an unmounted component.(while the component is mounted and visible) – Dan Aug 16 '22 at 17:02

1 Answers1

1

The simplest way to do this is probably to keep track of when a button is first pressed and then when it is released, and using a useRef to keep track of a time interval for how often the button action should trigger. That will look like this:

const intervalRef = useRef(null);

Then we need a function to handle when we begin pressing the plus button:

  const startCountUp = () => {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => {
      setTempValue((prevCounter) => prevCounter + 1);
    }, 10);
  };

We'll add a similar function for when the user presses the minus button, which counts down:

  const startCountDown = () => {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => {
      setTempValue((prevCounter) => prevCounter - 1);
    }, 10);
  };

Next, we need a way to stop incrementing the counter when we release the button. We'll create a function to handle stopping the timer:

  const stopCounter = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };

Then we need to trigger our functions properly when the user interacts with a button. Our button's onMouseDown should call startCountUp (for the + button), and when the user releases the mouse we want to use onMouseUp to call stopCounter so we stop incrementing the counter. Also, we want to be prepared for the case where the user moves their mouse outside of the button, so we use onMouseLeave to also call stopCounter.

All put together, here's what that looks like:

  const [tempValue, setTempValue] = useState(0);
  const intervalRef = useRef(null);

  const startCountUp = () => {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => {
      setTempValue((prevCounter) => prevCounter + 1);
    }, 10);
  };

  const startCountDown = () => {
    if (intervalRef.current) return;
    intervalRef.current = setInterval(() => {
      setTempValue((prevCounter) => prevCounter - 1);
    }, 10);
  };

  const stopCounter = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };

  useEffect(() => {
    return () => stopCounter(); // when App is unmounted we should stop counter
  }, []);

  return (
    <div className="App">
      <div>
        <button
          onMouseDown={startCountDown}
          onMouseUp={stopCounter}
          onMouseLeave={stopCounter}
        >
          <span>−</span>
        </button>
        <input
          name="custom-input-number"
          value={tempValue}
          onChange={(e) => {
            setTempValue(e.target.value);
          }}
        ></input>
        <button
          onMouseDown={startCountUp}
          onMouseUp={stopCounter}
          onMouseLeave={stopCounter}
        >
          <span>+</span>
        </button>
      </div>
    </div>
  );

And here's a full example of that on CodeSandbox: Edit React Click and Hold Button

Andrew Hulterstrom
  • 1,563
  • 4
  • 18