useThrottledEventHandler.js 2.54 KB
Newer Older
Sangjune Bae's avatar
Sangjune Bae committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

import { useRef } from 'react';
import useMounted from './useMounted';
import useEventCallback from './useEventCallback';

var isSyntheticEvent = function isSyntheticEvent(event) {
  return typeof event.persist === 'function';
};

/**
 * Creates a event handler function throttled by `requestAnimationFrame` that
 * returns the **most recent** event. Useful for noisy events that update react state.
 *
 * ```tsx
 * function Component() {
 *   const [position, setPosition] = useState();
 *   const handleMove = useThrottledEventHandler<React.PointerEvent>(
 *     (event) => {
 *       setPosition({
 *         top: event.clientX,
 *         left: event.clientY,
 *       })
 *     }
 *   )
 *
 *   return (
 *     <div onPointerMove={handleMove}>
 *        <div style={position} />
 *     </div>
 *   );
 * }
 * ```
 *
 * @param handler An event handler function
 * @typeParam TEvent The event object passed to the handler function
 * @returns The event handler with a `clear` method attached for clearing any in-flight handler calls
 *
 */
export default function useThrottledEventHandler(handler) {
  var isMounted = useMounted();
  var eventHandler = useEventCallback(handler);
  var nextEventInfoRef = useRef({
    event: null,
    handle: null
  });

  var clear = function clear() {
    cancelAnimationFrame(nextEventInfoRef.current.handle);
    nextEventInfoRef.current.handle = null;
  };

  var handlePointerMoveAnimation = function handlePointerMoveAnimation() {
    var next = nextEventInfoRef.current;

    if (next.handle && next.event) {
      if (isMounted()) {
        next.handle = null;
        eventHandler(next.event);
      }
    }

    next.event = null;
  };

  var throttledHandler = function throttledHandler(event) {
    if (!isMounted()) return;

    if (isSyntheticEvent(event)) {
      event.persist();
    } // Special handling for a React.Konva event which reuses the
    // event object as it bubbles, setting target
    else if ('evt' in event) {
        event = _extends({}, event);
      }

    nextEventInfoRef.current.event = event;

    if (!nextEventInfoRef.current.handle) {
      nextEventInfoRef.current.handle = requestAnimationFrame(handlePointerMoveAnimation);
    }
  };

  throttledHandler.clear = clear;
  return throttledHandler;
}