mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-03 19:12:50 +01:00
fix(favorites): sync empty-state on cache hits to avoid blank view
Switching Favorites<->Watchlist (props swap in place, no remount) left the page blank with only the tab buttons: the empty-state was written as a side effect inside the queryFn, which React Query skips on cache hits, and a reset effect then cleared it — so on a cache-served switch nothing repopulated it, every list was hidden by hideIfEmpty, and the empty message never rendered. Each list now reports emptiness once its query settles (incl. cache hits) via a new optional onEmptyStateChange callback on InfiniteScrollingCollectionList (mobile + TV), reporting null while loading so a switch never flashes a stale state. The parent derives the aggregate empty-state from that (null = not settled yet, so the message stays hidden during a switch). The reset effect is removed entirely, not kept: React runs child effects before parent effects within a commit, so a parent reset setEmptyState(false) would clobber the children's reported values on the same render. Dropping it and using the per-list callbacks (tri-state null/true/false) is what makes the switch — including a cache-served one — resolve to the correct state. Applies to both mobile and TV Favorites.
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
useInfiniteQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import { useSegments } from "expo-router";
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -42,6 +42,13 @@ interface Props extends ViewProps {
|
||||
isFirstSection?: boolean;
|
||||
onItemFocus?: (item: BaseItemDto) => void;
|
||||
parentId?: string;
|
||||
/**
|
||||
* Reports emptiness whenever the query settles (incl. cache hits):
|
||||
* `null` while loading (unknown), otherwise whether the list is empty.
|
||||
* Lets a parent derive an aggregate empty-state reactively instead of via a
|
||||
* queryFn side effect, which React Query skips when it serves cache.
|
||||
*/
|
||||
onEmptyStateChange?: (isEmpty: boolean | null) => void;
|
||||
}
|
||||
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
@@ -123,6 +130,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
isFirstSection = false,
|
||||
onItemFocus,
|
||||
parentId,
|
||||
onEmptyStateChange,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
@@ -182,6 +190,14 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
return deduped;
|
||||
}, [data]);
|
||||
|
||||
// Report emptiness on every settle (incl. cache hits). Callback held in a ref
|
||||
// so an inline parent callback doesn't retrigger the effect each render.
|
||||
const onEmptyStateChangeRef = useRef(onEmptyStateChange);
|
||||
onEmptyStateChangeRef.current = onEmptyStateChange;
|
||||
useEffect(() => {
|
||||
onEmptyStateChangeRef.current?.(isLoading ? null : allItems.length === 0);
|
||||
}, [isLoading, allItems.length]);
|
||||
|
||||
const itemWidth =
|
||||
orientation === "horizontal" ? posterSizes.episode : posterSizes.poster;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user