react long press event react long press event reactjs reactjs

react long press event


I've created a codesandbox with a hook to handle long press and click. Basically, on mouse down, touch start events, a timer is created with setTimeout. When the provided time elapses, it triggers long press.On mouse up, mouse leave, touchend, etc, the timer is cleared.

useLongPress.js

import { useCallback, useRef, useState } from "react";const useLongPress = (    onLongPress,    onClick,    { shouldPreventDefault = true, delay = 300 } = {}    ) => {    const [longPressTriggered, setLongPressTriggered] = useState(false);    const timeout = useRef();    const target = useRef();    const start = useCallback(        event => {            if (shouldPreventDefault && event.target) {                    event.target.addEventListener("touchend", preventDefault, {                    passive: false                });                target.current = event.target;            }            timeout.current = setTimeout(() => {                onLongPress(event);                setLongPressTriggered(true);            }, delay);        },        [onLongPress, delay, shouldPreventDefault]    );    const clear = useCallback(        (event, shouldTriggerClick = true) => {            timeout.current && clearTimeout(timeout.current);            shouldTriggerClick && !longPressTriggered && onClick();            setLongPressTriggered(false);            if (shouldPreventDefault && target.current) {                target.current.removeEventListener("touchend", preventDefault);            }        },        [shouldPreventDefault, onClick, longPressTriggered]    );    return {        onMouseDown: e => start(e),        onTouchStart: e => start(e),        onMouseUp: e => clear(e),        onMouseLeave: e => clear(e, false),        onTouchEnd: e => clear(e)    };};const isTouchEvent = event => {return "touches" in event;};const preventDefault = event => {if (!isTouchEvent(event)) return;if (event.touches.length < 2 && event.preventDefault) {    event.preventDefault();}};export default useLongPress;

To use the hook,App.js

import useLongPress from "./useLongPress";export default function App() {    const onLongPress = () => {        console.log('longpress is triggered');    };    const onClick = () => {        console.log('click is triggered')    }    const defaultOptions = {        shouldPreventDefault: true,        delay: 500,    };    const longPressEvent = useLongPress(onLongPress, onClick, defaultOptions);    return (        <div className="App">            <button {...longPressEvent}>use  Loooong  Press</button>        </div>    );}

Older answer for class components:

You can use MouseDown, MouseUp, TouchStart, TouchEnd events to control timers that can act as a long press event. Check out the code below

class App extends Component {  constructor() {    super()    this.handleButtonPress = this.handleButtonPress.bind(this)    this.handleButtonRelease = this.handleButtonRelease.bind(this)  }  handleButtonPress () {    this.buttonPressTimer = setTimeout(() => alert('long press activated'), 1500);  }    handleButtonRelease () {    clearTimeout(this.buttonPressTimer);  }  render() {    return (      <div           onTouchStart={this.handleButtonPress}           onTouchEnd={this.handleButtonRelease}           onMouseDown={this.handleButtonPress}           onMouseUp={this.handleButtonRelease}           onMouseLeave={this.handleButtonRelease}>        Button      </div>    );  }}


With hooks in react 16.8 you could rewrite class with functions and hooks.

import { useState, useEffect } from 'react';export default function useLongPress(callback = () => {}, ms = 300) {  const [startLongPress, setStartLongPress] = useState(false);  useEffect(() => {    let timerId;    if (startLongPress) {      timerId = setTimeout(callback, ms);    } else {      clearTimeout(timerId);    }    return () => {      clearTimeout(timerId);    };  }, [callback, ms, startLongPress]);  return {    onMouseDown: () => setStartLongPress(true),    onMouseUp: () => setStartLongPress(false),    onMouseLeave: () => setStartLongPress(false),    onTouchStart: () => setStartLongPress(true),    onTouchEnd: () => setStartLongPress(false),  };}
import useLongPress from './useLongPress';function MyComponent (props) {  const backspaceLongPress = useLongPress(props.longPressBackspaceCallback, 500);  return (    <Page>      <Button {...backspaceLongPress}>        Click me      </Button>    </Page>  );};


Nice hook! But I would like make a small improvement. Using useCallback to wrap event handlers. This ensures these will not changed on every render.

import { useState, useEffect, useCallback } from 'react';export default function useLongPress(callback = () => {}, ms = 300) {  const [startLongPress, setStartLongPress] = useState(false);  useEffect(() => {    let timerId;    if (startLongPress) {      timerId = setTimeout(callback, ms);    } else {      clearTimeout(timerId);    }    return () => {      clearTimeout(timerId);    };  }, [callback, ms, startLongPress]);  const start = useCallback(() => {    setStartLongPress(true);  }, []);  const stop = useCallback(() => {    setStartLongPress(false);  }, []);  return {    onMouseDown: start,    onMouseUp: stop,    onMouseLeave: stop,    onTouchStart: start,    onTouchEnd: stop,  };}