javascriptreactjsreact-hooksintersection-observer

IntersectionObserver with React & Hooks


I'm trying to track element visibility with React/Hooks and the Intersection Observer API. However, I can't figure out how to set up observation with "useEffect". Does anybody have any idea how could I do that? Mine solution does not work...

function MainProvider({ children }) {
  const [targetToObserve, setTargetToObserve] = useState([]);

  window.addEventListener("load", () => {
    const findTarget = document.querySelector("#thirdItem");
    setTargetToObserve([findTarget]);
  });

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.intersectionRatio === 0.1) {
          console.log("It works!");
        }
      },
      {
        root: null,
        rootMargin: "0px",
        threshold: 0.1
      }
    );
    if (targetToObserve.current) {
      observer.observe(targetToObserve.current);
    }
  }, []);

  return (
    <main>
     <div className="Section-item" id="firstItem"></div>
     <div className="Section-item" id="secondItem"></div>
     <div className="Section-item" id="thirdItem"></div>
    </main>
  );
}

Solution

  • JavaScript

    Hook

    import { useEffect, useState, useRef } from 'react';
    
    export function useOnScreen(ref) {
      const [isOnScreen, setIsOnScreen] = useState(false);
      const observerRef = useRef(null);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) =>
          setIsOnScreen(entry.isIntersecting)
        );
      }, []);
    
      useEffect(() => {
        observerRef.current.observe(ref.current);
    
        return () => {
          observerRef.current.disconnect();
        };
      }, [ref]);
    
      return isOnScreen;
    }
    
    
    

    Usage:

    import { useRef } from 'react';
    import useOnScreen from './useOnScreen';
    
    function MyComponent() {
      const elementRef = useRef(null);
      const isOnScreen = useOnScreen(elementRef);
    
      console.log({isOnScreen});
    
      return (
        <div>
          <div style={{ paddingBottom: '140vh' }}>scroll to element...</div>
          <div ref={elementRef}>my element</div>
        </div>
      );
    }
    
    

    TypeScript

    Hook

    import { useEffect, useState, useRef, RefObject } from 'react';
    
    export default function useOnScreen(ref: RefObject<HTMLElement>) {
      const observerRef = useRef<IntersectionObserver | null>(null);
      const [isOnScreen, setIsOnScreen] = useState(false);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) =>
          setIsOnScreen(entry.isIntersecting)
        );
      }, []);
    
      useEffect(() => {
        observerRef.current.observe(ref.current);
    
        return () => {
          observerRef.current.disconnect();
        };
      }, [ref]);
    
      return isOnScreen;
    }
    
    

    Usage:

    import { useRef } from 'react';
    import useOnScreen from './useOnScreen';
    
    function MyComponent() {
      const elementRef = useRef<HTMLDivElement>(null);
      const isOnScreen = useOnScreen(elementRef);
    
      console.log({isOnScreen});
    
      return (
        <div>
          <div style={{ paddingBottom: '140vh' }}>scroll to element...</div>
          <div ref={elementRef}>my element</div>
        </div>
      );
    }
    
    

    https://codesandbox.io/s/useonscreen-uodb1?file=/src/useOnScreen.ts