Hone logo
Hone
Problems

React useOnScreen Hook Challenge

This challenge asks you to create a custom React hook, useOnScreen, that determines whether a given DOM element is currently visible within the user's viewport. This is a fundamental hook for implementing features like lazy loading, infinite scrolling, and scroll-triggered animations, significantly improving user experience and performance.

Problem Description

You need to develop a TypeScript React hook named useOnScreen. This hook will take a ref to a DOM element as an argument and return a boolean value indicating whether that element is currently visible within the browser's viewport.

Key Requirements:

  • The hook should accept a React.RefObject<T> where T is a DOM element type (e.g., HTMLDivElement).
  • The hook should return true if any part of the referenced element is visible in the viewport, and false otherwise.
  • The hook should update its return value in real-time as the user scrolls or resizes the window.
  • The hook should clean up any event listeners when the component unmounts to prevent memory leaks.

Expected Behavior:

When a component uses useOnScreen with a ref to an element, the boolean returned by the hook should accurately reflect the element's visibility. For example, if the element is off-screen, the hook should return false. As the user scrolls the page and the element enters the viewport, the hook should return true.

Edge Cases:

  • The initial state of the element when the component first renders.
  • The element might be initially off-screen but become visible later.
  • The element might be partially visible.
  • Window resizing can affect visibility.
  • The provided ref might be null or undefined initially.

Examples

Example 1: Basic Usage

Input (Conceptual):

Imagine a component that renders a div and passes its ref to useOnScreen.

import React, { useRef } from 'react';
import useOnScreen from './useOnScreen'; // Assume this is your hook

function MyComponent() {
  const elementRef = useRef<HTMLDivElement>(null);
  const isVisible = useOnScreen(elementRef);

  return (
    <div style={{ height: '100vh' }}> {/* Placeholder to create scroll */}
      <div
        ref={elementRef}
        style={{
          height: '200px',
          width: '200px',
          backgroundColor: isVisible ? 'lightgreen' : 'lightcoral',
          marginTop: '50vh', // Position to be initially off-screen
        }}
      >
        {isVisible ? 'I am visible!' : 'I am hidden.'}
      </div>
    </div>
  );
}

Output (Conceptual):

  • When the component initially loads and the div is below the fold (not in the viewport):
    • isVisible will be false.
    • The div will have a lightcoral background and display "I am hidden."
  • When the user scrolls down and the div enters the viewport:
    • isVisible will become true.
    • The div will have a lightgreen background and display "I am visible!".

Example 2: Handling Initial Visibility

Input (Conceptual):

A component where the target div is initially within the viewport.

import React, { useRef } from 'react';
import useOnScreen from './useOnScreen';

function AnotherComponent() {
  const elementRef = useRef<HTMLDivElement>(null);
  const isVisible = useOnScreen(elementRef);

  return (
    <div style={{ height: '50vh' }}> {/* Just enough space for some content */}
      <div
        ref={elementRef}
        style={{
          height: '100px',
          width: '100px',
          backgroundColor: isVisible ? 'lightblue' : 'lightgray',
          position: 'fixed', // Keep it in view
          top: '10px',
          left: '10px',
        }}
      >
        {isVisible ? 'Always visible!' : 'Should not be hidden.'}
      </div>
    </div>
  );
}

Output (Conceptual):

  • Upon initial render, the div is within the viewport.
    • isVisible will be true.
    • The div will have a lightblue background and display "Always visible!".

Constraints

  • The hook must be implemented in TypeScript.
  • The hook should leverage the IntersectionObserver API for optimal performance.
  • Avoid using direct window.addEventListener('scroll', ...) calls if IntersectionObserver can be used instead.
  • The hook should be performant, especially when used with many elements.

Notes

  • Consider using IntersectionObserver as it is the modern and efficient way to detect element visibility.
  • The ref passed to the hook might not have its current property available immediately on the first render. Your hook should handle this gracefully.
  • Think about how to manage the observer instance (creation and cleanup).
  • The IntersectionObserver API provides options like rootMargin and threshold that you might want to consider exposing or using default values for. For this challenge, focusing on basic visibility is sufficient.
Loading editor...
typescript