Merge chore/code-hygiene: fix Plex infinite-scroll dead after drill-in→Back

Callback-ref the grid's IntersectionObserver sentinel so it re-attaches on the
grid's unmount/remount cycle (drill into info/show/season → Back), instead of
observing a detached old node and freezing the feed at the loaded pages.
This commit is contained in:
npeter83 2026-07-12 00:16:54 +02:00
commit 1df6ddd154

View file

@ -156,21 +156,24 @@ export default function PlexBrowse({
// Episode matches (grouped "Episodes" section) — search-only; same set on every page, take page 0. // Episode matches (grouped "Episodes" section) — search-only; same set on every page, take page 0.
const episodes = browseQ.data?.pages[0]?.episodes ?? []; const episodes = browseQ.data?.pages[0]?.episodes ?? [];
// Infinite scroll: auto-load the next page when the sentinel scrolls into view. // Infinite scroll: auto-load the next page when the sentinel scrolls into view. The sentinel lives
const sentinel = useRef<HTMLDivElement>(null); // in the grid branch, which UNMOUNTS when we drill into an info/show/season page and remounts on
// Back — as a NEW element. A callback ref (state, not useRef) re-runs this effect on every
// mount/unmount, so the observer always watches the live node; a plain ref would keep observing the
// detached old node after Back and never fire fetchNextPage again (feed stuck at the loaded pages).
const [sentinelEl, setSentinelEl] = useState<HTMLDivElement | null>(null);
const { hasNextPage, isFetchingNextPage, fetchNextPage } = browseQ; const { hasNextPage, isFetchingNextPage, fetchNextPage } = browseQ;
useEffect(() => { useEffect(() => {
const el = sentinel.current; if (!sentinelEl) return;
if (!el) return;
const io = new IntersectionObserver( const io = new IntersectionObserver(
(entries) => { (entries) => {
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) fetchNextPage(); if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) fetchNextPage();
}, },
{ rootMargin: "600px" }, { rootMargin: "600px" },
); );
io.observe(el); io.observe(sentinelEl);
return () => io.disconnect(); return () => io.disconnect();
}, [hasNextPage, isFetchingNextPage, fetchNextPage]); }, [sentinelEl, hasNextPage, isFetchingNextPage, fetchNextPage]);
function onCard(card: PlexCard) { function onCard(card: PlexCard) {
scrollRef.current = scroller()?.scrollTop ?? 0; // remember grid position for the trip back scrollRef.current = scroller()?.scrollTop ?? 0; // remember grid position for the trip back
@ -345,7 +348,7 @@ export default function PlexBrowse({
</> </>
)} )}
<div ref={sentinel} className="h-8" /> <div ref={setSentinelEl} className="h-8" />
{isFetchingNextPage && <p className="text-center text-xs text-muted pb-4">{t("plex.loading")}</p>} {isFetchingNextPage && <p className="text-center text-xs text-muted pb-4">{t("plex.loading")}</p>}
</div> </div>
); );