asnaeb
asnaeb

Reputation: 745

IntersectionObserver not updating in React

The following code will trigger a state change as soon as the element enters or leaves the viewport by scrolling

const dialog = useRef(),
[visible, set] = useState(false)

useEffect(() => {
        const observer = new IntersectionObserver((e) => {
            set(e[0].isIntersecting ? true : false)
        }, {root: null, rootMargin: '0px', threshold: 0})
        observer.observe(dialog.current)
        return () => observer.disconnect()
    }, [dialog.current])

return <div ref={dialog}>my dialog</div>

but if I try something like this

const observer = new IntersectionObserver((e) => {
            set(e[0].boundingClientRect.y <= 0 ? true : false)
        }, {root: null, rootMargin: '0px', threshold: 0})

the state gets updated only if a re-render is triggered, while I want it to trigger when I scroll the viewport, based on the boundingClientRect.y value of the observed element. the only way I found to make this work is by using window.onscroll event inside useEffect and putting the observer inside it but it doesn't look too good to me. Is there a different (and better) way than using window.onscroll ?

Upvotes: 0

Views: 5441

Answers (1)

dvnfvm
dvnfvm

Reputation: 26

This is to do with the DOM content not fully loading by the time the IntersectionObserver is instantiated in the useEffect hook.

A way around this, without using window.onscroll, is to run the hook in a component and pass your setter as a prop.

Something like this

function MyObserver({ selector, callback }){
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => callback(entries),
      { root: null, rootMargin: '0px', threshold: 0 });

    const element = document.querySelector(selector);

    observer.observe(element);

    return () => {
      observer.disconnect();
    };
      
  }, []);


  return null;
}

function Main() {
  [visible, set] = useState(false);

  return <>
    <div className="myDialog">my dialog</div>
    <MyObserver selector=".myDialog"
      callback={ (e) => { set(e[0].isIntersecting ? true : false); } } />
  </>;
}

Now the observer logic is part of the render flow, taking place after the dialog, so it should update correctly.

This worked for me when faced with this exact issue.

Upvotes: 1

Related Questions