Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 1x 1x 3x 3x 1x 6x 6x 6x 6x 6x 5x 6x 7x 5x 5x 5x 6x 11x 11x 11x 11x 11x 11x 11x 11x 9x 9x 11x 8x 8x 7x 4x 4x 3x 3x 3x 3x 7x 11x | import { useCallback, useEffect, useRef } from "react";
import { logEvent } from "./eventCollector";
/**
* Returns a stable `logEvent` function for use in components.
*/
export function useLogEvent() {
return useCallback(
(
eventType: string,
properties?: Record<string, unknown>,
value?: number,
) => {
logEvent(eventType, properties, value);
},
[],
);
}
/**
* Returns [start, stop] callbacks for timing an event.
* On `stop()`, logs the event with value = duration in seconds.
*
* `properties` is stored in a ref so callers don't need to memoize it.
* `stop` accepts optional `extraProperties` for dynamic data at measurement time.
*/
export function useDurationEvent(
eventType: string,
properties: Record<string, unknown> = {},
) {
const startTimeRef = useRef<number | null>(null);
const propsRef = useRef(properties);
propsRef.current = properties;
const start = useCallback(() => {
startTimeRef.current = performance.now();
}, []);
const stop = useCallback(
(extraProperties?: Record<string, unknown>) => {
if (startTimeRef.current === null) return;
const durationS = (performance.now() - startTimeRef.current) / 1000;
startTimeRef.current = null;
logEvent(
eventType,
{ ...propsRef.current, ...extraProperties },
durationS,
);
},
[eventType],
);
return [start, stop] as const;
}
/**
* Returns a callback ref. When the element enters the viewport,
* fires the event once via IntersectionObserver.
*
* `properties` and `threshold` are stored in refs so callers don't
* need to memoize them — the observer is only recreated when `eventType` changes.
*/
export function useImpressionRef(
eventType: string,
properties: Record<string, unknown> = {},
options?: { threshold?: number },
) {
const observerRef = useRef<IntersectionObserver | null>(null);
const hasFiredRef = useRef(false);
const propsRef = useRef(properties);
propsRef.current = properties;
const thresholdRef = useRef(options?.threshold ?? 0.5);
thresholdRef.current = options?.threshold ?? 0.5;
// Cleanup on unmount
useEffect(() => {
return () => {
observerRef.current?.disconnect();
};
}, []);
const callbackRef = useCallback(
(node: HTMLElement | null) => {
// Disconnect previous observer
observerRef.current?.disconnect();
if (!node || hasFiredRef.current) return;
observerRef.current = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting && !hasFiredRef.current) {
hasFiredRef.current = true;
logEvent(eventType, propsRef.current);
observerRef.current?.disconnect();
break;
}
}
},
{ threshold: thresholdRef.current },
);
observerRef.current.observe(node);
},
[eventType],
);
return callbackRef;
}
|