Skip to content

Instantly share code, notes, and snippets.

@jacob-ebey
Created September 7, 2022 02:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacob-ebey/970d8082df1660bace3121c94980afd3 to your computer and use it in GitHub Desktop.
Save jacob-ebey/970d8082df1660bace3121c94980afd3 to your computer and use it in GitHub Desktop.
Remix Deferred Infinite Scrolling
import * as React from "react";
import { defer, type LoaderArgs } from "@remix-run/node";
import {
Await,
Link,
Outlet,
useLoaderData,
useLocation,
useNavigate,
useTransition,
useSearchParams,
type ShouldReloadFunction,
} from "@remix-run/react";
import useInfiniteScroll from "react-infinite-scroll-hook";
import { pokemonClient } from "~/pokemon";
import {
Panel,
PanelHeader,
PanelItem,
PanelItemLink,
PanelMain,
} from "~/components/panel";
import iconsHref from "~/icons.svg";
const PER_PAGE = 30;
export function loader({ request }: LoaderArgs) {
const url = new URL(request.url);
let page = Number(url.searchParams.get("page"));
page = Number.isSafeInteger(page) ? page : 0;
const skip = page * PER_PAGE;
return defer({
page,
pokemon: pokemonClient.listPokemons(skip, PER_PAGE).then((result) => {
return result.results.map((pokemon) => {
return {
id: pokemon.url.split("/").slice(-2, -1)[0],
name: pokemon.name,
};
});
}),
});
}
export const unstable_shouldReload: ShouldReloadFunction = ({
url,
prevUrl,
}) => {
return (
!!url.searchParams.get("page") &&
url.searchParams.get("page") !== prevUrl.searchParams.get("page")
);
};
export default function PokemonList() {
const loaderData = useLoaderData<typeof loader>();
const location = useLocation();
const navigate = useNavigate();
const transition = useTransition();
const [searchParams] = useSearchParams();
const forceShow = location.pathname === "/dashboard/pokemon";
const panelOpen = searchParams.get("open") === "list";
const currentPage = searchParams.get("page");
const nextPage = new URLSearchParams(transition.location?.search).get("page");
const loadingNextPage = !!nextPage && nextPage !== currentPage;
const [loadedPages, setLoadedPages] = React.useState<
[number, Awaited<typeof loaderData["pokemon"]>][]
>([]);
React.useEffect(() => {
let canceled = false;
loaderData.pokemon.then((pokemons) => {
if (canceled) return;
setLoadedPages((loadedPages) => [
...loadedPages.filter((page) => page[0] !== loaderData.page),
[loaderData.page, pokemons],
]);
});
return () => {
canceled = true;
};
}, [loaderData.pokemon, loaderData.page]);
const pokemonToRender = React.useMemo(() => {
if (!loadedPages.length) return loaderData.pokemon;
const pages = loadedPages.sort(([a], [b]) => a - b);
return pages.flatMap(([, pokemons]) => pokemons);
}, [loaderData.pokemon, loadedPages]);
const [sentryRef] = useInfiniteScroll({
loading: transition.state === "loading",
hasNextPage: true,
onLoadMore: () => {
console.log("LOADING MORE");
navigate(`?page=${loaderData.page + 1}`, { replace: true });
},
});
return (
<>
<Panel size="sm" open={panelOpen} force={forceShow}>
<PanelHeader>
<Link to="?open=menu" className="icon mr xl:hidden">
<svg height={20} width={20}>
<use href={iconsHref + "#menu"} />
</svg>
</Link>
Pokemon
</PanelHeader>
<PanelMain>
<React.Suspense>
<Await resolve={pokemonToRender}>
{(pokemons) => {
return (
<>
{loaderData.page > 0 && (
<PanelItemLink
replace
to={`?page=${loaderData.page - 1}#${pokemons[0]?.id}`}
>
Load Previous
</PanelItemLink>
)}
{pokemons.map((pokemon, index) => (
<PanelItemLink
key={pokemon.id}
to={pokemon.id}
id={pokemon.id}
>
<img
ref={
index === pokemons.length - 1
? sentryRef
: undefined
}
height={24}
width={24}
className="mr"
src={`/pokemon-sprites/${pokemon.id}.svg`}
alt=""
/>
<span className="capitalize flex flex-center">
{pokemon.name}
</span>
</PanelItemLink>
))}
</>
);
}}
</Await>
</React.Suspense>
{loadingNextPage && (
<PanelItem>
<span className="capitalize flex flex-center">Loading...</span>
</PanelItem>
)}
</PanelMain>
</Panel>
<Outlet />
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment