import { useState, useEffect } from "react"; /* Creates an Intersection Observer to keep track of headings intersecting * a section of the viewport defined by the rootMargin */ const getIntersectionObserver = (callback) => { return new IntersectionObserver( (entries) => { entries.forEach((entry) => { callback(entry); }); }, { root: null, threshold: 0.55, rootMargin: "-65px 0% -85% 0%", // 65px is a navbar height } ); }; const useHeadingsObserver = () => { const [activeHeading, setActiveHeading] = useState(""); const [observer, setObserver] = useState(null); /* Runs only after the first render, in order to preserve the observer * between component rerenderings (e.g. after activeHeading change). */ useEffect(() => { const observer = getIntersectionObserver((entry) => { if (entry.isIntersecting) { setActiveHeading(entry.target.id); } }); setObserver(observer); return () => { observer.disconnect(); }; }, []); /* On initial render activeHeading will be `null`, since the observer * has not been instantiated yet. However, we still want to highlight * the current heading in the ToC based on the current url. */ useEffect(() => { if (!activeHeading) { return; } const tocLink = document.querySelector( `.toc-link[href="#${activeHeading}"]` ); tocLink.classList.add("active"); return () => { tocLink.classList.remove("active"); }; }, [activeHeading]); return observer; }; export default useHeadingsObserver;