Skip to content

Commit

Permalink
feat: load more before loading is in view
Browse files Browse the repository at this point in the history
  • Loading branch information
sehnryr committed Nov 10, 2024
1 parent 7dfe114 commit d58bda0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 42 deletions.
1 change: 0 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"react": "npm:[email protected]",
"react-dom": "npm:[email protected]",
"react-icons": "npm:[email protected]",
"react-infinite-scroll-hook": "npm:[email protected]",
"react-intersection-observer": "npm:[email protected]",
"react-router-dom": "npm:[email protected]",
"tailwindcss": "npm:[email protected]",
Expand Down
17 changes: 0 additions & 17 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 26 additions & 24 deletions src/pages/Browse/ExtensionBrowse.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { forwardRef, useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import useInfiniteScroll from "react-infinite-scroll-hook";
import { useInView } from "react-intersection-observer";

import { Extension } from "../../types/extension.ts";
import { Manga } from "../../types/manga.ts";
import { getMangaList } from "../../services/extensions.service.ts";
import { useStore } from "../../services/store.service.ts";
import { downloadImage } from "../../services/tauri.service.ts";
import useInfiniteScroll from "../../utils/infinite-scroll-hook.ts";

export default function ExtensionBrowse() {
const { extensionId } = useParams();
const getExtension = useStore((state) => state.getExtension);

const [extension, setExtension] = useState<Extension | null>(null);
const [mangas, setMangas] = useState<Array<Manga>>([]);
const [loading, setLoading] = useState<boolean>(false);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(0);

Expand All @@ -28,7 +27,6 @@ export default function ExtensionBrowse() {

const loadMore = async () => {
if (!extension) return;
setLoading(true);
const [nextMangas, nextHasMore] = await getMangaList(
extension.id,
[],
Expand All @@ -37,31 +35,27 @@ export default function ExtensionBrowse() {
setMangas([...mangas, ...nextMangas]);
setHasMore(nextHasMore);
setPage(page + 1);
setLoading(false);
};

const [sentryRef] = useInfiniteScroll({
loading,
hasNextPage: hasMore,
const { containerRef, loading } = useInfiniteScroll({
hasMore: hasMore,
onLoadMore: loadMore,
offset: "50vh",
});

if (!extension) return <Loader />;

return (
<div className="px-1">
<ExtensionHeader extension={extension} />
<Grid>
<Grid ref={containerRef}>
{mangas.map((manga: Manga) => (
<GridItem key={manga.id}>
<MangaItem manga={manga} extensionId={extension.id} />
</GridItem>
))}
{(loading || hasMore) && (
<GridItem
ref={sentryRef}
className="col-span-full flex flex-col items-center justify-center"
>
<GridItem className="col-span-full flex flex-col items-center justify-center">
<Loader />
</GridItem>
)}
Expand All @@ -87,27 +81,35 @@ const ExtensionHeader = ({ extension }: { extension: Extension }) => (
</div>
);

const Grid = ({ children }: { children: React.ReactNode }) => (
<ul className="grid grid-cols-[repeat(auto-fill,minmax(100px,5fr))] gap-3">
{children}
</ul>
);

type GridItemProps = {
type GridProps = {
children: React.ReactNode;
className?: string;
};

const GridItem = forwardRef<React.ComponentRef<"li">, GridItemProps>(
function GridItem(props: GridItemProps, ref: React.LegacyRef<HTMLLIElement>) {
const Grid = forwardRef<React.ComponentRef<"ul">, GridProps>(
function Grid(props: GridProps, ref: React.LegacyRef<HTMLUListElement>) {
return (
<li ref={ref} className={props.className}>
<ul
ref={ref}
className="grid grid-cols-[repeat(auto-fill,minmax(100px,5fr))] gap-3"
>
{props.children}
</li>
</ul>
);
},
);

const GridItem = (
{ children, key, className }: {
children: React.ReactNode;
key?: string;
className?: string;
},
) => (
<li key={key} className={className}>
{children}
</li>
);

const MangaItem = (
{ manga, extensionId }: { manga: Manga; extensionId: string },
) => {
Expand Down
53 changes: 53 additions & 0 deletions src/utils/infinite-scroll-hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { LegacyRef, useEffect, useRef, useState } from "react";

export default function useInfiniteScroll({
hasMore,
onLoadMore,
offset = "0px",
}: {
hasMore: boolean;
onLoadMore: () => Promise<unknown>;
offset?: string;
}) {
const [loading, setLoading] = useState(false);
const observerRef = useRef<IntersectionObserver>();
const targetRef = useRef((() => {
const target = document.createElement("div");
target.setAttribute("data-infinite-scroll-detector", "");
target.style.position = "absolute";
target.style.bottom = "0px";
target.style.width = "0px";
target.style.height = offset;
return target;
})());

const containerRef: LegacyRef<HTMLElement> = (container: HTMLElement) => {
if (container) {
container.append(targetRef.current);
container.style.position = "relative";
}
};

useEffect(() => {
const observer = observerRef.current;
if (observer) {
observer.disconnect();
}

observerRef.current = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting && !loading && hasMore) {
setLoading(true);
await onLoadMore();
setLoading(false);
}
});

observerRef.current.observe(targetRef.current);

return () => {
observerRef.current?.disconnect();
};
}, [hasMore, onLoadMore, loading]);

return { containerRef, loading };
}

0 comments on commit d58bda0

Please sign in to comment.