Prevent click events on double click with React (with and without Hooks)
This post proposes two implementations (one with a HOC, and the other one with a Hook) that solve the problem of React triggering the onClick
event twice before triggering the onDoubleClick
when a pointing device is double-clicked.
If you already know about this problem, you can skip the next section and jump directly to the solution.
Handling both onDoubleClick and onClick events
React allows us to handle DOM events in React elements. Equivalent to click
event and dblclick
event, in React we can configure, for an element:
onClick
: callback that will be executed when aclick
event is triggered.onDoubleClick
: callback that will be executed when adblclick
event is triggered.
Look at the example below of a <ClickableBox>
component that allows the configuration of both handlers:
If we make a double-click on the <ClickableBox>
, we get this in the browser console:
Like browsers, React triggers two single click
events before triggering the doubleclick
event.
How can we avoid the click
event to be executed twice when a doubleclick
happens?
Solution
The solution lies in delaying the execution of the onClick
handler until we know that the click is not part of a double-click: The onClick
handlers are delayed for a while in a queue of pending promises and, when a dblclick
event is triggered, all the pending promises are cancelled so they will never be executed.
Cancellable promise
To handle the pending promises, we will use cancellable promises. I’ve already talked about the cancellable promises in a the post “Avoid updates on unmounted React components”, you can check it out if you haven’t!
A cancellable promise is, basically, a wrapped promise that can be cancelled (rejected with extra info to know the rejection happened because of a cancellation). Below the implementation:
Below the tools we are going to import from /utils in the following implementations:
Solution using a HOC
There’re some notes all over the implementation to make it easier to follow:
We can use pleaseStopTriggeringClicksOnDoubleClick
HOC with any component:
Solution using Hooks
With Hooks, we can extract to a custom Hook both logics: handling cancellable promises and redefining onClick
and onDoubleClick
handlers.
To extract the logic of handling cancellable promises, we define a custom Hook that holds a ref with the array of pending promises, defines the api to interact with this array, and returns the api.
We don’t store the pending promises in a state variable because we don’t want the component to be re-rendered every time this array changes.
To extract the logic of redefining onClick
and onDoubleClick
handlers, we define another custom Hook that, given both handlers, redefine them to implement the solution described above using the useCancellablePromises
Hook:
Any functional component can use the useClickPreventionOnDoubleClick
Hook easily:
Summing up
If you want to handle both onClick
and onDoubleClick
, you will probably need to control the onClick
handler to not be triggered when the click
event comes from a dblClick
event. You can use the solution proposed in this post choosing the one that suits you best, the one with a HOC for your class components or the one using custom Hooks for your functional components.