Clean up fetch story by allowing hook fetch

This commit is contained in:
Zoe Roux 2023-10-25 00:47:26 +02:00
parent 034f048883
commit e8b929d4ca
4 changed files with 93 additions and 85 deletions

View File

@ -104,7 +104,7 @@ export const EpisodeList = <Props,>({
}: { }: {
slug: string; slug: string;
season: string | number; season: string | number;
Header: ComponentType<Props & { children: JSX.Element; empty: boolean }>; Header: ComponentType<Props & { children: JSX.Element }>;
headerProps: Props; headerProps: Props;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -21,10 +21,10 @@
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models"; import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { useBreakpointMap, HR } from "@kyoo/primitives"; import { useBreakpointMap, HR } from "@kyoo/primitives";
import { FlashList } from "@shopify/flash-list"; import { FlashList } from "@shopify/flash-list";
import { ComponentType, isValidElement, ReactElement, useRef } from "react"; import { ComponentProps, ComponentType, isValidElement, ReactElement, useRef } from "react";
import { EmptyView, ErrorView, Layout, WithLoading, addHeader } from "./fetch"; import { EmptyView, ErrorView, Layout, WithLoading, addHeader } from "./fetch";
export const InfiniteFetch = <Data, Props, _>({ export const InfiniteFetchList = <Data, Props, _>({
query, query,
placeholderCount = 15, placeholderCount = 15,
incremental = false, incremental = false,
@ -33,11 +33,11 @@ export const InfiniteFetch = <Data, Props, _>({
empty, empty,
divider = false, divider = false,
Header, Header,
headerProps: hprops, headerProps,
getItemType, getItemType,
...props ...props
}: { }: {
query: QueryIdentifier<_, Data>; query: ReturnType<typeof useInfiniteFetch<_, Data>>;
placeholderCount?: number; placeholderCount?: number;
layout: Layout; layout: Layout;
horizontal?: boolean; horizontal?: boolean;
@ -48,27 +48,16 @@ export const InfiniteFetch = <Data, Props, _>({
empty?: string | JSX.Element; empty?: string | JSX.Element;
incremental?: boolean; incremental?: boolean;
divider?: boolean | ComponentType; divider?: boolean | ComponentType;
Header?: ComponentType<Props & { children: JSX.Element; empty: boolean }> | ReactElement; Header?: ComponentType<Props & { children: JSX.Element }> | ReactElement;
headerProps?: Props; headerProps?: Props;
getItemType?: (item: Data, index: number) => string | number; getItemType?: (item: Data, index: number) => string | number;
}): JSX.Element | null => { }): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const { numColumns, size } = useBreakpointMap(layout); const { numColumns, size } = useBreakpointMap(layout);
const oldItems = useRef<Data[] | undefined>(); const oldItems = useRef<Data[] | undefined>();
let { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } = useInfiniteFetch( let { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } = query;
query,
{
useErrorBoundary: false,
},
);
if (incremental && items) oldItems.current = items; if (incremental && items) oldItems.current = items;
if (error) return <ErrorView error={error} />; if (error) return <ErrorView error={error} />;
// @ts-ignore
const headerProps: Props & { empty: boolean } = hprops
? { ...hprops, empty: items?.length === 0 }
: { empty: items?.length === 0 };
if (empty && items && items.length === 0) { if (empty && items && items.length === 0) {
if (typeof empty !== "string") return addHeader(Header, empty, headerProps); if (typeof empty !== "string") return addHeader(Header, empty, headerProps);
return addHeader(Header, <EmptyView message={empty} />, headerProps); return addHeader(Header, <EmptyView message={empty} />, headerProps);
@ -101,3 +90,17 @@ export const InfiniteFetch = <Data, Props, _>({
/> />
); );
}; };
export const InfiniteFetch = <Data, Props, _>({
query,
...props
}: {
query: QueryIdentifier<_, Data>;
} & Omit<ComponentProps<typeof InfiniteFetchList<Data, Props, _>>, "query">) => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const ret = useInfiniteFetch(query, {
useErrorBoundary: false,
});
return <InfiniteFetchList query={ret} {...props} />;
};

View File

@ -21,6 +21,7 @@
import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models"; import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models";
import { HR } from "@kyoo/primitives"; import { HR } from "@kyoo/primitives";
import { import {
ComponentProps,
ComponentType, ComponentType,
Fragment, Fragment,
isValidElement, isValidElement,
@ -131,7 +132,7 @@ const InfiniteScroll = <Props,>({
); );
}; };
export const InfiniteFetch = <Data, _, HeaderProps>({ export const InfiniteFetchList = <Data, _, HeaderProps>({
query, query,
incremental = false, incremental = false,
placeholderCount = 15, placeholderCount = 15,
@ -140,11 +141,11 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
empty, empty,
divider: Divider = false, divider: Divider = false,
Header, Header,
headerProps: hprops, headerProps,
getItemType, getItemType,
...props ...props
}: { }: {
query: QueryIdentifier<_, Data>; query: ReturnType<typeof useInfiniteFetch<_, Data>>;
incremental?: boolean; incremental?: boolean;
placeholderCount?: number; placeholderCount?: number;
layout: Layout; layout: Layout;
@ -158,18 +159,10 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
headerProps: HeaderProps; headerProps: HeaderProps;
getItemType?: (item: Data, index: number) => string | number; getItemType?: (item: Data, index: number) => string | number;
}): JSX.Element | null => { }): JSX.Element | null => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const oldItems = useRef<Data[] | undefined>(); const oldItems = useRef<Data[] | undefined>();
const { items, error, fetchNextPage, hasNextPage, isFetching } = useInfiniteFetch(query, { const { items, error, fetchNextPage, hasNextPage, isFetching } = query;
useErrorBoundary: false,
});
if (incremental && items) oldItems.current = items; if (incremental && items) oldItems.current = items;
// @ts-ignore
const headerProps: HeaderProps & { empty: boolean } = hprops
? { ...hprops, empty: items?.length === 0 }
: { empty: items?.length === 0 };
if (error) return addHeader(Header, <ErrorView error={error} />, headerProps); if (error) return addHeader(Header, <ErrorView error={error} />, headerProps);
if (empty && items && items.length === 0) { if (empty && items && items.length === 0) {
if (typeof empty !== "string") return addHeader(Header, empty, headerProps); if (typeof empty !== "string") return addHeader(Header, empty, headerProps);
@ -201,3 +194,17 @@ export const InfiniteFetch = <Data, _, HeaderProps>({
</InfiniteScroll> </InfiniteScroll>
); );
}; };
export const InfiniteFetch = <Data, Props, _>({
query,
...props
}: {
query: QueryIdentifier<_, Data>;
} & Omit<ComponentProps<typeof InfiniteFetchList<Data, Props, _>>, "query">) => {
if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch.");
const ret = useInfiniteFetch(query, {
useErrorBoundary: false,
});
return <InfiniteFetchList query={ret} {...props} />;
};

View File

@ -25,6 +25,7 @@ import {
LibraryItemP, LibraryItemP,
QueryIdentifier, QueryIdentifier,
getDisplayDate, getDisplayDate,
useInfiniteFetch,
} from "@kyoo/models"; } from "@kyoo/models";
import { H3, IconButton, ts } from "@kyoo/primitives"; import { H3, IconButton, ts } from "@kyoo/primitives";
import { ReactElement, forwardRef, useRef } from "react"; import { ReactElement, forwardRef, useRef } from "react";
@ -33,74 +34,69 @@ import { px, useYoshiki } from "yoshiki/native";
import { ItemGrid } from "../browse/grid"; import { ItemGrid } from "../browse/grid";
import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg"; import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg";
import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg"; import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
import { InfiniteFetch } from "../fetch-infinite"; import { InfiniteFetch, InfiniteFetchList } from "../fetch-infinite";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const Header = forwardRef< export const Header = ({ title }: { title: string }) => {
View,
{ empty: boolean; displayEmpty: boolean; genre: Genre; children: ReactElement }
>(function Header({ empty, displayEmpty, genre, children }, ref) {
const { css } = useYoshiki(); const { css } = useYoshiki();
return ( return (
<View ref={ref}> <View
{!(empty && !displayEmpty) && ( {...css({
<View marginTop: ItemGrid.layout.gap,
{...css({ marginX: ItemGrid.layout.gap,
marginTop: ItemGrid.layout.gap, pX: ts(0.5),
marginX: ItemGrid.layout.gap, flexDirection: "row",
pX: ts(0.5), justifyContent: "space-between",
flexDirection: "row", })}
justifyContent: "space-between", >
})} <H3>{title}</H3>
> {/* <View {...css({ flexDirection: "row" })}> */}
<H3>{genre}</H3> {/* <IconButton */}
{/* <View {...css({ flexDirection: "row" })}> */} {/* icon={ChevronLeft} */}
{/* <IconButton */} {/* // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} */}
{/* icon={ChevronLeft} */} {/* /> */}
{/* // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} */} {/* <IconButton */}
{/* /> */} {/* icon={ChevronRight} */}
{/* <IconButton */} {/* // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} */}
{/* icon={ChevronRight} */} {/* /> */}
{/* // onPress={() => ref.current?.scrollTo({ x: 0, animated: true })} */} {/* </View> */}
{/* /> */}
{/* </View> */}
</View>
)}
{children}
</View> </View>
); );
}); };
export const GenreGrid = ({ genre }: { genre: Genre }) => { export const GenreGrid = ({ genre }: { genre: Genre }) => {
const query = useInfiniteFetch(GenreGrid.query(genre));
const displayEmpty = useRef(false); const displayEmpty = useRef(false);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<InfiniteFetch <>
query={GenreGrid.query(genre)} {(displayEmpty.current || query.items?.length !== 0) && <Header title={genre} />}
layout={{ ...ItemGrid.layout, layout: "horizontal" }} <InfiniteFetchList
empty={displayEmpty.current ? t("home.none") : undefined} query={query}
Header={Header} layout={{ ...ItemGrid.layout, layout: "horizontal" }}
headerProps={{ genre, displayEmpty: displayEmpty.current }} empty={displayEmpty.current ? t("home.none") : undefined}
> headerProps={{ title: genre, displayEmpty: displayEmpty.current }}
{(x, i) => { >
// only display empty list if a loading as been displayed (not durring ssr) {(x, i) => {
if (x.isLoading) displayEmpty.current = true; // only display empty list if a loading as been displayed (not durring ssr)
return ( if (x.isLoading) displayEmpty.current = true;
<ItemGrid return (
key={x.id ?? i} <ItemGrid
isLoading={x.isLoading as any} key={x.id ?? i}
href={x.href} isLoading={x.isLoading as any}
name={x.name} href={x.href}
subtitle={ name={x.name}
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined subtitle={
} x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
poster={x.poster} }
/> poster={x.poster}
); />
}} );
</InfiniteFetch> }}
</InfiniteFetchList>
</>
); );
}; };
@ -111,5 +107,7 @@ GenreGrid.query = (genre: Genre): QueryIdentifier<LibraryItem> => ({
params: { params: {
genres: genre, genres: genre,
sortBy: "random", sortBy: "random",
// Limit the inital numbers of items
limit: 10,
}, },
}); });