x-feed
x-feed

Reputation: 57

Start interval after time delay and stop on button release / React

I am trying to build a simple plus/minus-control in React. When clicked on either plus or minus (triggered by onMouseDown) the value should change by a defined step and when the button is held the value should in-/decrease at a specified interval after a specified delay. When the button is released (onMouseUp), the interval should stop.

The code below runs ok on onMouseDown and hold, but when I just click on the button the interval starts anyway. I see that I need to make sure that the button is still down before the interval is started, but how do I achieve that? Thank you for any insights.

let plusTimer = useRef(null);

const increment = () => {
  setMyValue(prev => prev + myStep);
  setTimeout(() => {
    plusTimer.current = setInterval(
      () => setMyValue(prev => prev + myStep),
      100
    );
  }, 500);
};

const intervalClear = () => {
  clearInterval(plusTimer.current);
};

Upvotes: 1

Views: 221

Answers (1)

Peter Lehnhardt
Peter Lehnhardt

Reputation: 4995

I think I will let the code speak for itself:

const {useCallback, useEffect, useState} = React;

const CASCADE_DELAY_MS = 1000;
const CASCADE_INTERVAL_MS = 100;

function useDelayedCascadeUpdate(intervalTime, delay, step, callback) {
  const [started, setStarted] = useState(false);
  const [running, setRunning] = useState(false);
  const update = useCallback(() => callback((count) => count + step), [
    callback,
    step
  ]);
  const handler = useCallback(() => {
    update();
    setStarted(true);
  }, [update, setStarted]);
  const reset = useCallback(() => {
    setStarted(false);
    setRunning(false);
  }, [setStarted, setRunning]);

  useEffect(() => {
    if (started) {
      const handler = setTimeout(() => setRunning(true), delay);
      return () => {
        clearTimeout(handler);
      };
    }
  }, [started, setRunning, delay]);
  useEffect(() => {
    if (running) {
      const handler = setInterval(update, intervalTime);
      return () => {
        clearInterval(handler);
      };
    }
  }, [running, update, intervalTime]);

  return [handler, reset];
}

function App() {
  const [count, setCount] = useState(0);
  const [incrementHandler, incrementReset] = useDelayedCascadeUpdate(
    CASCADE_INTERVAL_MS,
    CASCADE_DELAY_MS,
    1,
    setCount
  );
  const [decrementHandler, decrementReset] = useDelayedCascadeUpdate(
    CASCADE_INTERVAL_MS,
    CASCADE_DELAY_MS,
    -1,
    setCount
  );

  return (
    <div>
      <div>{count}</div>
      <button onMouseDown={incrementHandler} onMouseUp={incrementReset}>
        +
      </button>
      <button onMouseDown={decrementHandler} onMouseUp={decrementReset}>
        -
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

Upvotes: 1

Related Questions