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 だから無限ローディング形式じゃないと難しいということもない。
たぶんページを分けたほうが良さそうな気がする。

以上