Implementing hold to confirm button in React | Karol Działowski

Implementing hold to confirm button in React

In this blog post, I will demonstrate how to create a straightforward "hold to confirm" button using vanilla React. Furthermore, I will delve into the reasons why this UX pattern might not always be the most optimal choice. Lastly, I'll illustrate the application of this button within my side project, where its purpose is to prevent unintentional deletions.

Motivation

Let's begin with the core concept. My goal is to implement a "hold to confirm" button, which would activate an action only after several seconds of continuous holding. This approach serves to mitigate unintended triggering of destructive actions (though I'll delve into its limitations later).

Hold to confirm explained.

Hold to confirm explained.

Implementation

To make this happen, we need to watch for two actions related to the button: (1) when the user clicks and holds the button (onMouseDown), and (2) when the user releases the button (onMouseUp).

When the user clicks and holds the button (onMouseDown), we start a timer by noting the current time. We also set up a regular check (interval) to see if enough time has passed for the intended action to be triggered.

When the user releases the button (onMouseUp), we stop the timer by resetting the recorded time and clear the interval that was set up earlier.

Let's begin with the simple version:

const HOLD_DELAY = 2000 // 2 seconds of holding needed to trigger action

export const HoldToConfirmButton = ({ onSubmit }) => {
  const startTime = React.useRef(null)
  const holdIntervalRef = React.useRef(null)

  const startCounter = () => {
    // Set startTime to current time
    startTime.current = Date.now()

    // We will check if the timer is finished in the interval
    holdIntervalRef.current = setInterval(() => {
      // Check if enough time has elapsed
      if (startTime.current && Date.now() - startTime.current > HOLD_DELAY) {
        // When 2 seconds elapsed clear interval and trigger callback
        stopCounter()
        onSubmit()
      }
    }, 10)
  }

  const stopCounter = () => {
    startTime.current = null
    if (holdIntervalRef.current) {
      clearInterval(holdIntervalRef.current)
      holdIntervalRef.current = null
    }
  }

  return (
    <button
      onMouseDown={startCounter} // When user clicks the button we start the counter
      onMouseUp={stopCounter} // When user lift the button we stop the counter
    >
      Hold me to confirm
    </button>
  )
}

But here's a concern with the current code. The user isn't sure how much longer they need to keep holding the button. To tackle this, we can add another piece of information called the "percentage" state, which will modify the button's background color accordingly. You can access the complete code on codesandbox.

Critique

The most significant drawback of this approach is its limited familiarity among users.

Users might not immediately grasp that holding the button is necessary instead of just clicking it. A potential solution could involve implementing a popover that appears when the user clicks (but doesn't hold) the button. An example of this can be seen in my time-task-tracker project, as demonstrated below:

Another challenge arises concerning accessibility. Ensuring that this button remains user-friendly for keyboard interactions is not a straightforward task.

While it might initially provide a more favorable experience compared to a confirmation dialog, the reality is that the undo action proves to be a more efficient solution. It could become frustrating if, for instance, you're attempting to delete numerous items and are required to wait for a 2-second delay with each deletion. My suggestion is to reserve the use of the "hold to confirm" approach for exceptional cases, such as in the time-task-tracker project, where it's employed to wipe app data.

Considering alternative UX patterns could be a prudent move. These include: (1) utilizing a confirmation dialog, (2) incorporating undo actions, and (3) employing a confirmation dialog coupled with text input.

Type to confirm pattern on GitHub.

Type to confirm pattern on GitHub.

References

© karlosos 2020 Open sourced on