import type { MetricAttributes } from 'expo-app-metrics'; import { useEffect, useRef, useState } from 'react'; import { useObserve } from './useObserve'; export type ObserveInteractiveMarkerProps = { /** * Custom parameters attached to the TTI metric, forwarded to `markInteractive`. * Values can be strings, numbers, booleans, or other JSON-serializable values. */ params?: MetricAttributes['params']; }; /** * Declarative wrapper around `useObserve().markInteractive(...)`. Renders nothing * and calls `markInteractive` once when it first mounts, marking the moment the * screen becomes interactive (used to compute the `tti` metric). Render it once * the screen is ready for user interaction — for example, after its initial data * has loaded. * * Because `markInteractive` is only sent on mount, the marker is fire-once: changing * `params` after the first render has no effect and warns in development. If you need * to attach attributes that are only known later, call `useObserve().markInteractive(...)` * imperatively instead. * * @example * ```tsx * import { ObserveInteractiveMarker } from 'expo-observe'; * * function Feed({ items }) { * if (!items) return ; * return ( * <> * * * * ); * } * ``` */ export function ObserveInteractiveMarker(props: ObserveInteractiveMarkerProps) { const { markInteractive } = useObserve(); const [initialProps] = useState(props); const hasWarnedRef = useRef(false); useEffect(() => { markInteractive({ params: initialProps.params }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!__DEV__ || hasWarnedRef.current) return; if (!paramsEqual(initialProps.params, props.params)) { hasWarnedRef.current = true; console.warn( '[expo-observe] received changed props after its first render. ' + 'markInteractive is only sent once on mount, so updates to `params` are ignored. ' + "Pass params that are stable for the marker's lifetime, or call " + 'useObserve().markInteractive(...) directly if you need to attach attributes known later.' ); } }, [props.params]); return null; } function paramsEqual( a: ObserveInteractiveMarkerProps['params'], b: ObserveInteractiveMarkerProps['params'] ) { try { return JSON.stringify(a) === JSON.stringify(b); } catch { return a === b; } }