79728753

Date: 2025-08-07 14:50:02
Score: 0.5
Natty:
Report link

It's above Sunyatasattva's answer, but I added a small logic to be compatible with Web Workers.

import { useCallback, useRef, useState } from 'react'

const DEFAULT_LONGPRESS_INTERVAL_MS = 700

function preventDefault(e: Event) {
    if (!isTouchEvent(e)) return

    if (e.touches.length < 2 && e.preventDefault) {
        if (typeof e.cancelable !== 'boolean' || e.cancelable) {
            e.preventDefault()
        }
    }
}

export function isTouchEvent(e: Event): e is TouchEvent {
    return e && 'touches' in e
}

interface PressHandlers<T> {
    onLongPress: (e: React.MouseEvent<T> | React.TouchEvent<T>) => void,
    onClick?: (e: React.MouseEvent<T> | React.TouchEvent<T>) => void,
}

interface Options {
    delay?: number
    shouldPreventDefault?: boolean
}

export default function useLongPress<T>(
    { onLongPress, onClick }: PressHandlers<T>,
    {
        delay = DEFAULT_LONGPRESS_INTERVAL_MS,
        shouldPreventDefault = true,
    } : Options = {}
) {
    const [longPressTriggered, setLongPressTriggered] = useState(false)
    const timeout = useRef<NodeJS.Timeout>()
    const target = useRef<EventTarget>()

    const start = useCallback((e: React.MouseEvent<T> | React.TouchEvent<T>) => {
        e.persist()
        const clonedEvent = { ...e }

        if (shouldPreventDefault && e.target) {
            e.target.addEventListener('touchend', preventDefault, { passive: false })
            target.current = e.target
        }

        timeout.current = setTimeout(() => {
            onLongPress(clonedEvent)
            setLongPressTriggered(true)
        }, delay)
    }, [onLongPress, delay, shouldPreventDefault])

    const clear = useCallback((
        e: React.MouseEvent<T> | React.TouchEvent<T>,
        shouldTriggerClick = true,
    ) => {
        timeout.current && clearTimeout(timeout.current)
        shouldTriggerClick && !longPressTriggered && onClick?.(e)

        setLongPressTriggered(false)

        if (shouldPreventDefault && target.current) {
            target.current.removeEventListener('touchend', preventDefault)
        }
    }, [shouldPreventDefault, onClick, longPressTriggered])

    return {
        onMouseDown: (e: React.MouseEvent<T>) => start(e),
        onTouchStart: (e: React.TouchEvent<T>) => start(e),
        onMouseUp: (e: React.MouseEvent<T>) => clear(e),
        onMouseLeave: (e: React.MouseEvent<T>) => clear(e, false),
        onTouchEnd: (e: React.TouchEvent<T>) => clear(e),
    }
}
Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: micttyoid