Reputation: 439
I'm using a React Hook to detect when an element is visible in the viewport. Until then, everything works fine but I need to 'reset' the state when everything is hidden (like reaching the footer or the header).
Here's my hook:
import { useState, useEffect, useRef } from 'react'
const useIntersect = (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
}
export default useIntersect
And here's the state I'm updating:
const elementRef = useRef(null)
const[state, setState] = useState('nothing is visible')
const onScreen = useIntersect(elementRef)
useEffect(() => {
return onScreen ? setState('an item is visible') : null
})
return (
<Item ref={elementRef}>
...
</Item>
)
I need to be able to say setState('...')
to something (let's say a string) when all the elements are off the viewport (when the condition onScreen is not met) but even when I reach the footer when nothing is visible, it keeps the last item in memory.
I've tried many things but I cannot figure a way out :(
Thanks for the help!
Upvotes: 3
Views: 819
Reputation: 196142
I believe your useIntersect
hook should add a dependency on the ref.current
instead of ref
for the second useEffect
so
useEffect(() => {
observerRef.current.observe(ref.current)
return () => observerRef.current.disconnect()
}, [ref.current]);
and then when using it in the component do
useEffect(() => {
return onScreen ? setState('an item is visible') : setState('nothing is visible');
}, [onScreen])
you should add a dependency on the onScreen
so it only updates when the onScreen
has changed, and lastly you should call setState
for the scenario when the onScreen
is false to the value you want.
Upvotes: 1
Reputation: 1480
Your hook implementation seems fine. you might want to recheck the height of the Item
component.
check this code sandbox working fine with your hook implementation
: https://codesandbox.io/s/elegant-wood-ruks2?file=/src/App.js
Upvotes: 1
Reputation: 10111
I think the issue is with how you are passing elementRef to the hook.
useIntersect useEffect will not execute when elementRef changes.
import { useState, useEffect, useRef } from "react";
const useIntersect = () => {
const ref = useRef(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
if (!ref) {
const observer = new IntersectionObserver(([entry]) =>
setIsOnScreen(entry.isIntersecting)
);
observer.observe(ref.current);
return () => observer.disconnect();
}
}, [ref]);
return [isOnScreen, ref];
};
export default useIntersect;
// -----------------------------Component-------------------------------
const [state, setState] = useState("nothing is visible");
const [onScreen, elementRef] = useIntersect();
useEffect(() => {
return onScreen ? setState("an item is visible") : null;
});
return <Item ref={elementRef}></Item>;
Upvotes: 1