18

I have a ReactJS component that I want to have different behavior on a single click and on a double click.

I read this question.

<Component
    onClick={this.onSingleClick}
    onDoubleClick={this.onDoubleClick} />

And I tried it myself and it appears as though you cannot register both single click and double click on a ReactJS component.

I'm not sure of a good solution to this problem. I don't want to use a timer because I'm going to have 8 of these single components on my page.

Would it be a good solution to have another inner component inside this one to deal with the double click situation?

Edit:

I tried this approach but it doesn't work in the render function.

render (

    let props = {};

    if (doubleClick) {
        props.onDoubleClick = function
    } else {
        props.onClick = function
    }
    <Component
        {...props} />
);
NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
user1261710
  • 2,539
  • 5
  • 41
  • 72
  • Just a suggestion, but double-clicks are easily handled with RxJS https://gist.github.com/anthonybrown/a5ef9148bd101157d922 – azium Feb 18 '16 at 20:11
  • The [answer](http://stackoverflow.com/a/25780754/135133) you link to already talks about how binding to both the click and dbclick event on a single dom element is inadvisable. Double clicking is not a common user interaction on the web which results it most users not knowing that you can. Using a different interaction method is probably the best approach. – PetersenDidIt Feb 18 '16 at 21:24
  • @PetersenDidIt yeah I think it's weird too but they want to let the user know what they only clicked one time? There is a double-click setting. – user1261710 Feb 18 '16 at 21:34
  • I added an answer to the question you linked showing how you can do it. – Jeff Fairley Aug 09 '16 at 18:40

5 Answers5

23

Here is the fastest and shortest answer:

CLASS-BASED COMPONENT


class DoubleClick extends React.Component {
    timer = null

    onClickHandler = event => {
        clearTimeout(this.timer);

        if (event.detail === 1) {
            this.timer = setTimeout(this.props.onClick, 200)
        } else if (event.detail === 2) {
            this.props.onDoubleClick()
        }
    }

    render() {
        return (
            <div onClick={this.onClickHandler}>
                {this.props.children}
            </div>
        )
    }
}

FUNCTIONAL COMPONENT


const DoubleClick = ({ onClick = () => { }, onDoubleClick = () => { }, children }) => {
    const timer = useRef()

    const onClickHandler = event => {
        clearTimeout(timer.current);

        if (event.detail === 1) {
            timer.current = setTimeout(onClick, 200)
        } else if (event.detail === 2) {
            onDoubleClick()
        }
    }

    return (
        <div onClick={onClickHandler}>
            {children}
        </div>
    )
}

DEMO

var timer;

function onClick(event) {
  clearTimeout(timer);
  
  if (event.detail === 1) {
    timer = setTimeout(() => {
      console.log("SINGLE CLICK");
    }, 200)

  } else if (event.detail === 2) {
    console.log("DOUBLE CLICK");
  }
}

document.querySelector(".demo").onclick = onClick;
.demo {
  padding: 20px 40px;
  background-color: #eee;
  user-select: none;
}
<div class="demo">
  Click OR Double Click Here
</div>
Anatol
  • 3,720
  • 2
  • 20
  • 40
  • 1
    Great solution. I would recommand also to save the `timer` function in a class non-state variable, and in function component in a `ref` - so the component could be used many times with different timer functions. – Bar Dec 03 '21 at 16:05
  • So clever, thanks! – chick3n0x07CC Jul 08 '23 at 18:14
13

I know this is an old question and i only shoot into the dark (did not test the code but i am sure enough it should work) but maybe this is of help to someone.

render() {
    let clicks = [];
    let timeout;

    function singleClick(event) {
        alert("single click");
    }

    function doubleClick(event) {
        alert("doubleClick");
    }

    function clickHandler(event) {
        event.preventDefault();
        clicks.push(new Date().getTime());
        window.clearTimeout(timeout);
        timeout = window.setTimeout(() => {
            if (clicks.length > 1 && clicks[clicks.length - 1] - clicks[clicks.length - 2] < 250) {
                doubleClick(event.target);
            } else {
                singleClick(event.target);
            }
        }, 250);
    }

    return (
        <a onClick={clickHandler}>
            click me
        </a>
    );
}

I am going to test this soon and in case update or delete this answer.

The downside is without a doubt, that we have a defined "double-click speed" of 250ms, which the user needs to accomplish, so it is not a pretty solution and may prevent some persons from being able to use the double click.

Of course the single click does only work with a delay of 250ms but its not possible to do it otherwise, you have to wait for the doubleClick somehow...

Rob
  • 3,333
  • 5
  • 28
  • 71
Philipp Wrann
  • 1,751
  • 3
  • 19
  • 29
8

All of the answers here are overcomplicated, you just need to use e.detail:

<button onClick={e => {
  if (e.detail === 1) handleClick();
  if (e.detail === 2) handleDoubleClick();
}}>
  Click me
</button>
NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
1

A simple example that I have been doing.

File: withSupportDoubleClick.js

let timer
let latestTouchTap = { time: 0, target: null }

export default function withSupportDoubleClick({ onDoubleClick = () => {}, onSingleClick = () => {} }, maxDelay = 300) {
  return (event) => {
    clearTimeout(timer)

    const touchTap = { time: new Date().getTime(), target: event.currentTarget }

    const isDoubleClick =
      touchTap.target === latestTouchTap.target && touchTap.time - latestTouchTap.time < maxDelay

    latestTouchTap = touchTap

    timer = setTimeout(() => {
      if (isDoubleClick) onDoubleClick(event)
      else onSingleClick(event)
    }, maxDelay)
  }
}

File: YourComponent.js

import React from 'react'
import withSupportDoubleClick from './withSupportDoubleClick'

export default const YourComponent = () => {

  const handleClick = withSupportDoubleClick({
    onDoubleClick: (e) => {
      console.log('double click', e)
    },
    onSingleClick: (e) => {
      console.log('single click', e)
    },
  })

  return (
    <div
      className="cursor-pointer"
      onClick={handleClick}
      onTouchStart={handleClick}
      tabIndex="0"
      role="button"
      aria-pressed="false"
    >
      Your content/button...
    </div>
  )
}
  • onTouchStart start is a touch event that fires when the user touches the element.
Ricardo Canelas
  • 2,280
  • 26
  • 21
-4

Why do you describe these events handler inside a render function? Try this approach:

const Component = extends React.Component {

  constructor(props) {
      super(props);
  }

  handleSingleClick = () => {
    console.log('single click');
  }

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

  render() {
    return (
      <div onClick={this.handleSingleClick}  onDoubleClick={this.handleDoubleClick}>
      </div>
    );
  }
};
ShaDeRzz
  • 109
  • 2