Remix と無限ローディングの相性が悪そう
掲示板みたいな、書き込み欄とコンテンツの表示が同一ページ内にあるデザインで無限ローディング的なことをしようとしたら、なんだか見た目がよくないコードになった。
app/routes/_index.tsx がこんな感じになったのだが
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/cloudflare";
import { useFetcher, useLoaderData } from "@remix-run/react";
import uniq from "lodash.uniq";
import { useEffect, useState } from "react";
import { Comment } from "~/components/Comment";
import { CommentForm, CommentFormAction } from "~/components/CommentForm";
import { type Post, getPosts } from "~/repository/posts";
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{
name: "description",
content: "Welcome to Remix on Cloudflare!",
},
];
};
export const loader = async ({ context, request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const before = url.searchParams.get("before");
const beforeId = before === null ? undefined : Number.parseInt(before);
return getPosts(context.cloudflare.env.DB, beforeId);
};
export const action = CommentFormAction;
export default function Index() {
const { comments: initComments, maybeHasBefore: initMaybeHasBefore } =
useLoaderData<typeof loader>();
const fetcher = useFetcher<typeof loader>();
const [comments, setComments] = useState(initComments);
const [maybeHasBefore, setMaybeHasBefore] = useState(initMaybeHasBefore);
useEffect(() => {
const fetchedData = fetcher.data;
if (!fetchedData) return;
setComments((prev) => getUniqueArray([...fetchedData.comments, ...prev]));
setMaybeHasBefore(fetchedData.maybeHasBefore);
}, [fetcher.data]);
useEffect(() => {
setComments((prev) => getUniqueArray([...prev, ...initComments]));
}, [initComments]);
return (
<div className="px-4 py-16 mx-auto prose flex flex-col gap-8">
<div>
{maybeHasBefore && (
<fetcher.Form method="get">
<button type="submit" name="before" value={comments[0].id}>
以前のデータを取得する
</button>
</fetcher.Form>
)}
<ul>
{comments.map((c) => (
<li id={`comment-${c.id}`} key={c.id}>
<Comment body={c.body} created={c.created} />
</li>
))}
</ul>
<CommentForm />
</div>
</div>
);
}
function getUniqueArray(combinedArray: Post[]) {
const newIds = uniq(combinedArray.map((c) => c.id));
const newArray = newIds
.map((id) => combinedArray.find((c) => c.id === id))
.filter((v): v is Post => v !== undefined);
return newArray;
}
何かを書き込むと useLoaderData
の返り値が更新され、「以前のデータを取得する」ボタンを押すと useFetcher
の返り値が更新される。
そしてこれらはそれぞれ「最新の10件」と「指定した id より前の 10 件」を取得するので合体させて表示する。
だけど、useLoaderData
の返り値が更新されたときにデータの重複が発生するので、重複を排除している。
このコードで動きはするが、見た感じがすごくダサい。
そもそも無限ローディング自体あんまり好きじゃないし、バックエンドも sqlite だから無限ローディング形式じゃないと難しいということもない。
たぶんページを分けたほうが良さそうな気がする。
以上