Skip to main content

useIsVisible Deep Dive

The useIsVisible hook provides a reactive way to detect if an element is currently within the viewport (or a specified root) using the Intersection Observer API.


Core Concepts

useIsVisible wraps the native IntersectionObserver to provide a React-friendly interface. It returns a setRef function (or a ref object) that you must attach to the element you want to observe.

const { setRef, isVisible } = useIsVisible({
rootMargin: '50px',
multiple: true
});

return (
<div ref={setRef}>
{isVisible ? 'I am visible!' : 'I am hidden...'}
</div>
);

Observer Management

To optimize performance, Archibald uses an internal observerMap. This ensures that multiple components using the same intersection options (like rootMargin or threshold) share a single IntersectionObserver instance instead of creating redundant ones.

  • Deduplication: Options are stringified and used as keys in the map.
  • Reference Counting: The system tracks how many elements are currently using a specific observer and disconnects the observer only when the last element is unobserved.

Common Options

multiple

  • false (Default): The observer is disconnected as soon as the element becomes visible for the first time. This is ideal for "reveal" animations or lazy-loading.
  • true: The hook continues to track visibility, updating isVisible every time the element enters or leaves the viewport.

rootMargin

Defines a set of offsets that effectively grow or shrink the area used for intersections. Default is '100px 0px 0px 0px'.

delay

Introduces a delay (in milliseconds) before updating the isVisible state. This can be used to prevent rapid flickering if an element is on the edge of the viewport.


How it Works Step-by-Step

  1. Mounting: The hook initializes with a default isVisible value.
  2. Ref Assignment: When setRef is attached to a DOM node, the registerElement function is triggered.
  3. Observer Retrieval: The hook looks for an existing IntersectionObserver in the observerMap that matches the provided options. If none exists, a new one is created.
  4. Observation: The DOM node is passed to the observer's .observe() method.
  5. Intersection Event: When the browser detects an intersection change:
    • The handleIntersect callback is fired.
    • If isIntersecting is true and multiple is false, the element is immediately unobserved.
    • A React startTransition is used to update the show state, ensuring the UI remains responsive.
  6. Unmounting: When the component unmounts (or the target element changes), the unregisterElement function is called, which decrements the reference count and potentially cleans up the global observer.

Best Practice: Use the multiple: false strategy for performance-intensive tasks like lazy-loading images or complex components to minimize the overhead of continuous intersection tracking.


Full Example

Basic Observer

import { useIsVisible } from '@archibald/client';

function TestComponent() {
const { isVisible, ref, setRef } = useIsVisible();

useEffect(() => {
if (isVisible) {
console.log('Element is now visible:', ref.current);
}
}, [isVisible]);

return <div ref={setRef}>Observe me!</div>;
}

Observing an External Element

import { useIsVisible } from '@archibald/client';
import { useSSR } from '@archibald/core';

function TestComponent() {
const { isBrowser } = useSSR();
const element = isBrowser ? document.getElementById('external-target') : null;
const { isVisible, ref } = useIsVisible({ element });

return (
<div>
<div id="external-target">Target Element</div>
<p>Is Target Visible? {isVisible ? 'Yes' : 'No'}</p>
</div>
);
}