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, };}