0

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 ?

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

that is a good solution and it works. I wonder if this is more or less resource consuming than window.onscroll
@soffyo _ Using window.onscroll in the way that you have implemented, a new IntersectionObserver is created every time the state of the DOM element of the dialog is changed. In the way I've expressed, it is only created once and not re-rendered. It's not conditionally rendered either. In terms of which is more performant, I think we may be discussing negligible performance in terms of UX. I don't know how much time it takes for a null-rendering React component to render, but I assume it's not too much more to create an event listener. This is my own opinion though. Up to you to decide.
Why not only set(e[0].isIntersecting) instead of set(e[0].isIntersecting ? true : false)?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.