/ src / hooks / useDoublePress.ts
useDoublePress.ts
 1  // Creates a function that calls one function on the first call and another
 2  // function on the second call within a certain timeout
 3  
 4  import { useCallback, useEffect, useRef } from 'react'
 5  
 6  export const DOUBLE_PRESS_TIMEOUT_MS = 800
 7  
 8  export function useDoublePress(
 9    setPending: (pending: boolean) => void,
10    onDoublePress: () => void,
11    onFirstPress?: () => void,
12  ): () => void {
13    const lastPressRef = useRef<number>(0)
14    const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
15  
16    const clearTimeoutSafe = useCallback(() => {
17      if (timeoutRef.current) {
18        clearTimeout(timeoutRef.current)
19        timeoutRef.current = undefined
20      }
21    }, [])
22  
23    // Cleanup timeout on unmount
24    useEffect(() => {
25      return () => {
26        clearTimeoutSafe()
27      }
28    }, [clearTimeoutSafe])
29  
30    return useCallback(() => {
31      const now = Date.now()
32      const timeSinceLastPress = now - lastPressRef.current
33      const isDoublePress =
34        timeSinceLastPress <= DOUBLE_PRESS_TIMEOUT_MS &&
35        timeoutRef.current !== undefined
36  
37      if (isDoublePress) {
38        // Double press detected
39        clearTimeoutSafe()
40        setPending(false)
41        onDoublePress()
42      } else {
43        // First press
44        onFirstPress?.()
45        setPending(true)
46  
47        // Clear any existing timeout and set new one
48        clearTimeoutSafe()
49        timeoutRef.current = setTimeout(
50          (setPending, timeoutRef) => {
51            setPending(false)
52            timeoutRef.current = undefined
53          },
54          DOUBLE_PRESS_TIMEOUT_MS,
55          setPending,
56          timeoutRef,
57        )
58      }
59  
60      lastPressRef.current = now
61    }, [setPending, onDoublePress, onFirstPress, clearTimeoutSafe])
62  }