Hone logo
Hone
Problems

React Intersection Observer Hook

This challenge asks you to create a custom React hook, useIntersectionObserver, that leverages the Intersection Observer API to efficiently detect when an element enters or exits the viewport (or a specified root element). This is a common requirement for implementing features like lazy loading, infinite scrolling, and triggering animations based on element visibility. Building this hook will solidify your understanding of React hooks and the Intersection Observer API.

Problem Description

You need to implement a useIntersectionObserver hook that takes a ref to a DOM element and an options object as arguments. The hook should return an object containing:

  • isIntersecting: A boolean value indicating whether the element is currently intersecting the viewport (or the specified root).
  • root: The root element used for intersection calculations.
  • rootMargin: The root margin used for intersection calculations.
  • threshold: The threshold used for intersection calculations.
  • observe: A function to start observing the element.
  • unobserve: A function to stop observing the element.

The hook should initialize an Intersection Observer instance and attach it to the element when the component mounts. It should update the isIntersecting state whenever the intersection status changes. When the component unmounts, the observer should be disconnected.

Key Requirements:

  • The hook must accept a ref to a DOM element.
  • The hook must accept an options object with properties for root, rootMargin, and threshold. Defaults should be used if these properties are not provided.
  • The hook must return an object with the specified properties.
  • The observer must be disconnected when the component unmounts.
  • The observe and unobserve functions should correctly start and stop the observer.

Expected Behavior:

  • When the component mounts, the hook should create an Intersection Observer and start observing the element.
  • When the element enters or exits the viewport (or the specified root), the isIntersecting state should be updated accordingly.
  • When the component unmounts, the hook should disconnect the observer to prevent memory leaks.
  • Calling observe should restart the observer.
  • Calling unobserve should stop the observer.

Edge Cases to Consider:

  • The element ref might be null or undefined initially.
  • The root element might be null or undefined.
  • The threshold might be an invalid value.
  • The element might be dynamically added or removed from the DOM.

Examples

Example 1:

Input:
<div ref={myElementRef}>Content</div>
<MyComponent />

Inside MyComponent:
const { isIntersecting, observe, unobserve } = useIntersectionObserver({
  root: null, // viewport
  threshold: 0.5,
});

Output: isIntersecting will be false initially. It will change to true when 50% or more of the element is visible in the viewport. observe and unobserve functions will control the observer's state.

Explanation: The hook initializes an Intersection Observer targeting the viewport (root: null) and triggering when 50% of the element is visible (threshold: 0.5).

Example 2:

Input:
<div ref={myElementRef}>Content</div>
<MyComponent />

Inside MyComponent:
const { isIntersecting, observe, unobserve } = useIntersectionObserver({
  root: document.getElementById('scrollContainer'),
  rootMargin: '100px',
  threshold: 1.0,
});

Output: isIntersecting will be false initially. It will change to true only when the entire element is visible within the 'scrollContainer' element, considering a 100px margin around the container.

Explanation: The hook initializes an Intersection Observer targeting the element with ID 'scrollContainer', with a 100px margin, and triggering only when the entire element is visible (threshold: 1.0).

Example 3: (Edge Case - Element not yet in DOM)

Input:
<MyComponent />

Inside MyComponent:
const { isIntersecting } = useIntersectionObserver({ root: null, threshold: 0 });

Output: isIntersecting will be false until the element referenced by the ref is added to the DOM.

Explanation: The hook will initialize, but isIntersecting will remain false until the ref is attached to a DOM element.

Constraints

  • The hook must be written in TypeScript.
  • The hook must not cause memory leaks.
  • The hook should be performant and avoid unnecessary re-renders.
  • The root option can be null (viewport), a DOM element, or a string representing a CSS selector. If a string is provided, it should be converted to a DOM element.
  • The threshold option should be a number between 0 and 1.

Notes

  • Consider using useEffect to manage the observer lifecycle.
  • The observe and unobserve functions can be implemented using a state variable to control the observer's active state.
  • Pay close attention to handling the case where the element ref is initially null or undefined.
  • Remember to disconnect the observer in the cleanup function of useEffect.
  • The rootMargin option allows you to adjust the boundaries of the intersection test.
Loading editor...
typescript