import {useCallback, useEffect, useRef} from 'react' import {useHandleAction} from './useHandleAction' import {useShopActions} from './useShopActions' export interface UseProductImpressionParams { /** * The product GID string to report impressions for */ productId: string /** * Whether to skip reporting impressions (e.g., when product data is loading) */ skip?: boolean } export interface UseProductImpressionReturns { /** * Ref to attach to the product element for visibility tracking. * When the element becomes visible, an impression will be reported. */ ref: {current: HTMLDivElement | null} } /** * Hook to report product impressions when a product becomes visible. * Attach the returned ref to the product container element. * * @example * ```tsx * function ProductCard({ product }) { * const { ref } = useProductImpression({ productId: product.id }); * return
...
; * } * ``` */ export function useProductImpression({ productId, skip = false, }: UseProductImpressionParams): UseProductImpressionReturns { const {productRecommendationImpression} = useShopActions() const handleAction = useHandleAction(productRecommendationImpression) const ref = useRef(null) const hasReportedRef = useRef(false) const reportImpression = useCallback(() => { if (hasReportedRef.current || skip || !productId) { return } hasReportedRef.current = true handleAction({productId}) }, [handleAction, productId, skip]) useEffect(() => { // Reset reported state when productId changes hasReportedRef.current = false }, [productId]) useEffect(() => { if (skip || !ref.current) { return } const element = ref.current const observer = new IntersectionObserver( entries => { const [entry] = entries if (entry?.isIntersecting) { reportImpression() observer.disconnect() } }, { threshold: 0.5, // Report when 50% of the element is visible } ) observer.observe(element) return () => { observer.disconnect() } }, [reportImpression, skip]) return {ref} }