Reputation: 38
My goal: Console log elements only if they are still visible
I have a list of elements that I'm passing to my component.
const list = [{id: 0, title: 'Title 0'}, {id: 1, title: 'Title 1'}, {id: 2, title: 'Title 2'}]
I want to render that list and observe each element.
const MyComponent = ({ list }) => {
const itemsRef = useRef([]);
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setTimeout(() => {
// check if element is still visible, if so - console.log it
}, 5000);
}
});
});
itemsRef.current.forEach(ref => observer.observe(ref));
return (
<>
<h1>This is my list</h1>
{list.map((el, index) => (
<h2
ref={(htmlEl) => { itemsRef.current[index] = htmlEl }}
key={el.id}
>
{el.title}
</h2>
))}
</>
);
};
Then, finally, if element is visible on my screen, I want to start counting time. After specific time, if element is still visible on the screen - I want to console.log it.
The issue is that elements that left my screen still appear as isIntersecting
. Also, I don't want to unobserve them when they leave screen, in case I would scroll up and then look at them for a specific time, but we can unobserve elements when they are console.log.
Upvotes: 2
Views: 1168
Reputation: 4600
It was interesting enough. To achieve your goal, I have created a HoC, a wrapper. This greatly simplifies the task and will reduce the likelihood of errors with the react lifecycle and refs management.
And just a note - in case child items will update their state and rerender - they will be removed from Map that was used due to HoC callback function and Map that is using "them", the "children"s as a key.
Not sure about memory leaks, didnt notice any in final result (and had a lot Out of Memory during development)
wrapper component:
import React, { useMemo, useCallback, useEffect, createRef } from "react";
const IntersectionObserverWrapper = (props) => {
const { children, onVisibilityChanged, onElementDestroyed } = props;
const wrapperRef = createRef();
const options = useMemo(() => {
return {
root: null,
rootMargin: "0px",
threshold: 0
};
}, []);
const onVisibilityChangedFn = useCallback(
(entries) => {
const [entry] = entries;
onVisibilityChanged?.(children, entry);
},
[children, onVisibilityChanged]
);
useEffect(() => {
const scoped = wrapperRef.current;
if (!scoped) return;
const observer = new IntersectionObserver(onVisibilityChangedFn, options);
observer.observe(scoped);
return () => {
observer.unobserve(scoped);
};
}, [options, onVisibilityChangedFn]);
useEffect(() => {
return () => onElementDestroyed?.(children);
}, [onElementDestroyed, children]);
return <div ref={wrapperRef}>{children}</div>;
};
export default IntersectionObserverWrapper;
actual component:
import React, { useCallback, useRef } from "react";
import IntersectionObserverWrapper from "../Wrappers/IntersectionObserverWrapper";
import "./IntersectionList.css";
const IntersectionList = ({ items }) => {
const visibilityMap = useRef(new Map());
const clearEntry = (element) => {
const entry = visibilityMap.current.get(element);
if (!entry) return;
const intervalId = entry.intervalId;
clearInterval(intervalId);
visibilityMap.current.delete(element);
};
const onVisibilityChanged = useCallback((element, entry) => {
const { isIntersecting } = entry;
if (isIntersecting) {
const intervalId = setInterval(() => {
const entry = visibilityMap.current.get(element);
if (!entry) {
console.warn("Something is wrong with Map and Interval");
return;
}
console.log(element);
}, 5000);
visibilityMap.current.set(element, {
lastSeenMs: Date.now(),
intervalId: intervalId
});
} else {
clearEntry(element);
}
}, []);
const onElementDestroyed = useCallback((element) => {
clearEntry(element);
}, []);
return (
<ul>
{items.map((x) => (
<IntersectionObserverWrapper
key={x.id}
onVisibilityChanged={onVisibilityChanged}
onElementDestroyed={onElementDestroyed}
>
<div className="obs-block" key={x.id}>
{x.id} - {x.title}
</div>
</IntersectionObserverWrapper>
))}
</ul>
);
};
export default IntersectionList;
I left the key for wrapped divs
just in order to track console log entries.
Upvotes: 2