onClick works but onDoubleClick is ignored on React component

JavascriptReactjs

Javascript Problem Overview


I am building a Minesweeper game with React and want to perform a different action when a cell is single or double clicked. Currently, the onDoubleClick function will never fire, the alert from onClick is shown. If I remove the onClick handler, onDoubleClick works. Why don't both events work? Is it possible to have both events on an element?

/** @jsx React.DOM */
 
var Mine = React.createClass({
  render: function(){
    return (
      <div className="mineBox" id={this.props.id} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}></div>
    )
  }
});
 
var MineRow = React.createClass({
  render: function(){
    var width = this.props.width,
        row = [];
    for (var i = 0; i < width; i++){
      row.push(<Mine id={String(this.props.row + i)} boxClass={this.props.boxClass} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}/>)
    }
    return (
      <div>{row}</div>
    )
  }
})
 
var MineSweeper = React.createClass({
  handleDoubleClick: function(){
    alert('Double Clicked');
  },
  handleClick: function(){
    alert('Single Clicked');
  },
  render: function(){
    var height = this.props.height,
        table = [];
    for (var i = 0; i < height; i++){
      table.push(<MineRow width={this.props.width} row={String.fromCharCode(97 + i)} onDoubleClick={this.handleDoubleClick} onClick={this.handleClick}/>)
    }
    return (
      <div>{table}</div>
    )
  }
})
 
var bombs = ['a0', 'b1', 'c2'];
React.renderComponent(<MineSweeper height={5} width={5} bombs={bombs}/>, document.getElementById('content'));

Javascript Solutions


Solution 1 - Javascript

This is not a limitation of React, it is a limitation of the DOM's click and dblclick events. As suggested by Quirksmode's click documentation:

> Don't register click and dblclick events on the same element: it's impossible to distinguish single-click events from click events that lead to a dblclick event.

For more current documentation, the W3C spec on the dblclick event states:

> A user agent must dispatch this event when the primary button of a pointing device is clicked twice over an element.

A double click event necessarily happens after two click events.

Edit:

One more suggested read is jQuery's dblclick handler:

> It is inadvisable to bind handlers to both the click and dblclick events for the same element. The sequence of events triggered varies from browser to browser, with some receiving two click events before the dblclick and others only one. Double-click sensitivity (maximum time between clicks that is detected as a double click) can vary by operating system and browser, and is often user-configurable.

Solution 2 - Javascript

The required result can be achieved by providing a very slight delay on firing off the normal click action, which will be cancelled when the double click event will happen.

  let timer = 0;
  let delay = 200;
  let prevent = false;

  doClickAction() {
    console.log(' click');
  }
  doDoubleClickAction() {
    console.log('Double Click')
  }
  handleClick() {
    let me = this;
    timer = setTimeout(function() {
      if (!prevent) {
        me.doClickAction();
      }
      prevent = false;
    }, delay);
  }
  handleDoubleClick(){
    clearTimeout(timer);
    prevent = true;
    this.doDoubleClickAction();
  }
 < button onClick={this.handleClick.bind(this)} 
    onDoubleClick = {this.handleDoubleClick.bind(this)} > click me </button>

Solution 3 - Javascript

You can use a custom hook to handle simple click and double click like this :

import { useState, useEffect } from 'react';

function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
    const [click, setClick] = useState(0);

    useEffect(() => {
        const timer = setTimeout(() => {
            // simple click
            if (click === 1) actionSimpleClick();
            setClick(0);
        }, delay);

        // the duration between this click and the previous one
        // is less than the value of delay = double-click
        if (click === 2) actionDoubleClick();

        return () => clearTimeout(timer);
        
    }, [click]);

    return () => setClick(prev => prev + 1);
}

then in your component you can use :

const click = useSingleAndDoubleClick(callbackClick, callbackDoubleClick);
<button onClick={click}>clic</button>

Solution 4 - Javascript

Instead of using ondoubleclick, you can use event.detail to get the current click count. It's the number of time the mouse's been clicked in the same area in a short time.

const handleClick = (e) => {
  switch (e.detail) {
    case 1:
      console.log("click");
      break;
    case 2:
      console.log("double click");
      break;
    case 3:
      console.log("triple click");
      break;
  }
};

return <button onClick={handleClick}>Click me</button>;

In the example above, if you triple click the button it will print all 3 cases:

click 
double click 
triple click 

Live Demo

Edit 25777826/onclick-works-but-ondoubleclick-is-ignored-on-react-component

Solution 5 - Javascript

Edit:

I've found that this is not an issue with React 0.15.3.


Original:

For React 0.13.3, here are two solutions.

1. ref callback

Note, even in the case of double-click, the single-click handler will be called twice (once for each click).

const ListItem = React.createClass({
 
  handleClick() {
    console.log('single click');
  },

  handleDoubleClick() {
    console.log('double click');
  },

  refCallback(item) {
    if (item) {
      item.getDOMNode().ondblclick = this.handleDoubleClick;
    }
  },

  render() {
    return (
      <div onClick={this.handleClick}
           ref={this.refCallback}>
      </div>
    );
  }
});

module.exports = ListItem;

2. lodash debounce

I had another solution that used lodash, but I abandoned it because of the complexity. The benefit of this was that "click" was only called once, and not at all in the case of "double-click".

import _ from 'lodash'

const ListItem = React.createClass({
 
  handleClick(e) {
    if (!this._delayedClick) {
      this._delayedClick = _.debounce(this.doClick, 500);
    }
    if (this.clickedOnce) {
      this._delayedClick.cancel();
      this.clickedOnce = false;
      console.log('double click');
    } else {
      this._delayedClick(e);
      this.clickedOnce = true;
    }
  },

  doClick(e) {
    this.clickedOnce = undefined;
    console.log('single click');
  },

  render() {
    return (
      <div onClick={this.handleClick}>
      </div>
    );
  }
});

module.exports = ListItem;

on the soapbox

I appreciate the idea that double-click isn't something easily detected, but for better or worse it IS a paradigm that exists and one that users understand because of its prevalence in operating systems. Furthermore, it's a paradigm that modern browsers still support. Until such time that it is removed from the DOM specifications, my opinion is that React should support a functioning onDoubleClick prop alongside onClick. It's unfortunate that it seems they do not.

Solution 6 - Javascript

Here's what I have done. Any suggestions for improvement are welcome.

class DoubleClick extends React.Component {
  state = {counter: 0}
  
  handleClick = () => {
   this.setState(state => ({
    counter: this.state.counter + 1,
  }))
 }


  handleDoubleClick = () => {
   this.setState(state => ({
    counter: this.state.counter - 2,
  }))
 }

 render() {
   return(
   <>
    <button onClick={this.handleClick} onDoubleClick={this.handleDoubleClick>
      {this.state.counter}
    </button>
   </>
  )
 }
}

Solution 7 - Javascript

This is the solution of a like button with increment and discernment values based on solution of Erminea.

useEffect(() => {
    let singleClickTimer;
    if (clicks === 1) {
      singleClickTimer = setTimeout(
        () => {
          handleClick();
          setClicks(0);
        }, 250);
    } else if (clicks === 2) {
      handleDoubleClick();
      setClicks(0);
    }
    return () => clearTimeout(singleClickTimer);
  }, [clicks]);

  const handleClick = () => {
    console.log('single click');
    total = totalClicks + 1;
    setTotalClicks(total);
  }

  const handleDoubleClick = () => {
    console.log('double click');
    if (total > 0) {
      total = totalClicks - 1;
    }
    setTotalClicks(total);
  }

  return (
    <div
      className="likeButton"
      onClick={() => setClicks(clicks + 1)}
    >
      Likes | {totalClicks}
    </div>
)

Solution 8 - Javascript

Here is one way to achieve the same with promises. waitForDoubleClick returns a Promise which will resolve only if double click was not executed. Otherwise it will reject. Time can be adjusted.

    async waitForDoubleClick() {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        if (!this.state.prevent) {
          resolve(true);
        } else {
          reject(false);
        }
      }, 250);
      this.setState({ ...this.state, timeout, prevent: false })
    });
  }
  clearWaitForDoubleClick() {
    clearTimeout(this.state.timeout);
    this.setState({
      prevent: true
    });
  }
  async onMouseUp() {
    try {
      const wait = await this.waitForDoubleClick();
      // Code for sinlge click goes here.
    } catch (error) {
      // Single click was prevented.
      console.log(error)
    }
  }

Solution 9 - Javascript

Here's my solution for React in TypeScript:

import { debounce } from 'lodash';

const useManyClickHandlers = (...handlers: Array<(e: React.UIEvent<HTMLElement>) => void>) => {
  const callEventHandler = (e: React.UIEvent<HTMLElement>) => {
    if (e.detail <= 0) return;
    const handler = handlers[e.detail - 1];
    if (handler) {
      handler(e);
    }
  };

  const debounceHandler = debounce(function(e: React.UIEvent<HTMLElement>) {
    callEventHandler(e);
  }, 250);

  return (e: React.UIEvent<HTMLElement>) => {
    e.persist();
    debounceHandler(e);
  };
};

And an example use of this util:

const singleClickHandler = (e: React.UIEvent<HTMLElement>) => {
  console.log('single click');
};
const doubleClickHandler = (e: React.UIEvent<HTMLElement>) => {
  console.log('double click');
};
const clickHandler = useManyClickHandlers(singleClickHandler, doubleClickHandler);

// ...

<div onClick={clickHandler}>Click me!</div>

Solution 10 - Javascript

I've updated Erminea Nea solution with passing an original event so that you can stop propagation + in my case I needed to pass dynamic props to my 1-2 click handler. All credit goes to Erminea Nea.

Here is a hook I've come up with:

import { useState, useEffect } from 'react';

const initialState = {
  click: 0,
  props: undefined
}

function useSingleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
  const [state, setState] = useState(initialState);

  useEffect(() => {
    const timer = setTimeout(() => {
    // simple click
      if (state.click === 1) actionSimpleClick(state.props);
      setState(initialState);
    }, delay);

    // the duration between this click and the previous one
    // is less than the value of delay = double-click
    if (state.click === 2) actionDoubleClick(state.props);

    return () => clearTimeout(timer);
      
  }, [state.click]);

  return (e, props) => {
    e.stopPropagation()

    setState(prev => ({
      click: prev.click + 1,
      props
    }))
  }
}

export default useSingleAndDoubleClick

Usage in some component:

const onClick = useSingleAndDoubleClick(callbackClick, callbackDoubleClick)

<button onClick={onClick}>Click me</button>

or

<button onClick={e => onClick(e, someOtherProps)}>Click me</button>

Solution 11 - Javascript

import React, { useState } from "react";

const List = () => {

const [cv, uv] = useState("nice");

  const ty = () => {

    uv("bad");

  };

  return (

    <>

      <h1>{cv}</h1>

      <button onDoubleClick={ty}>Click to change</button>

    </>

  );

};

export default List;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionthisisnotabusView Question on Stackoverflow
Solution 1 - JavascriptRoss AllenView Answer on Stackoverflow
Solution 2 - JavascriptAkanksha ChaturvediView Answer on Stackoverflow
Solution 3 - JavascriptErminea NeaView Answer on Stackoverflow
Solution 4 - JavascriptNearHuscarlView Answer on Stackoverflow
Solution 5 - JavascriptJeff FairleyView Answer on Stackoverflow
Solution 6 - JavascriptAlex MirelesView Answer on Stackoverflow
Solution 7 - JavascriptManosView Answer on Stackoverflow
Solution 8 - JavascriptDivineCoderView Answer on Stackoverflow
Solution 9 - JavascriptpapaiatisView Answer on Stackoverflow
Solution 10 - JavascriptStanislau BuzunkoView Answer on Stackoverflow
Solution 11 - JavascriptsumantaView Answer on Stackoverflow