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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 2x 2x 34x 34x 11x 6x 6x 6x 6x 6x 5x 6x 7x 5x 5x 5x 6x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 13x 13x 13x 1x 1x 15x 12x 12x 12x 11x 5x 5x 5x 5x 11x 11x 11x 11x 8x 3x 3x 5x 5x 2x 2x 3x 2x 2x 11x 15x | 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. Fires the event once via IntersectionObserver after
* the element has been continuously >= `threshold` visible for `minDurationMs`
* (default 0 = fires on first intersection).
*
* `properties`, `threshold`, and `minDurationMs` 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; minDurationMs?: number },
) {
const observerRef = useRef<IntersectionObserver | null>(null);
const visibleTimerRef = useRef<ReturnType<typeof setTimeout> | 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;
const minDurationRef = useRef(options?.minDurationMs ?? 0);
minDurationRef.current = options?.minDurationMs ?? 0;
// Cleanup on unmount
useEffect(() => {
return () => {
observerRef.current?.disconnect();
if (visibleTimerRef.current) {
clearTimeout(visibleTimerRef.current);
visibleTimerRef.current = null;
}
};
}, []);
const callbackRef = useCallback(
(node: HTMLElement | null) => {
// Disconnect previous observer
observerRef.current?.disconnect();
Iif (visibleTimerRef.current) {
clearTimeout(visibleTimerRef.current);
visibleTimerRef.current = null;
}
if (!node || hasFiredRef.current) return;
const fire = () => {
Iif (hasFiredRef.current) return;
hasFiredRef.current = true;
logEvent(eventType, propsRef.current);
observerRef.current?.disconnect();
};
observerRef.current = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
Iif (hasFiredRef.current) return;
if (entry.isIntersecting) {
if (minDurationRef.current === 0) {
fire();
return;
}
if (!visibleTimerRef.current) {
visibleTimerRef.current = setTimeout(() => {
visibleTimerRef.current = null;
fire();
}, minDurationRef.current);
}
} else if (visibleTimerRef.current) {
clearTimeout(visibleTimerRef.current);
visibleTimerRef.current = null;
}
}
},
{ threshold: thresholdRef.current },
);
observerRef.current.observe(node);
},
[eventType],
);
return callbackRef;
}
|