Open2

swrを利用した双方向ページネーション

yami-betayami-beta

swr には useSWRInfinite という hook があり、これを用いたページネーションが実装できる。
https://swr.vercel.app/docs/pagination#useswrinfinite

ただ useSWRInfinite は単方向のページネーションをサポートしているが、双方向のページネーションはサポートしていない。
ちなみに react-query では双方向のページネーションをサポートしている。
https://tanstack.com/query/latest/docs/react/guides/infinite-queries#what-if-i-want-to-implement-a-bi-directional-infinite-list

yami-betayami-beta

双方向のページネーションが必要な要件がある場合は react-query を採用すれば基本的には良いかなと思う。

ただ swr でなんとか双方向ページネーションに対応できないかなと試行錯誤していて、それっぽいものが出来た。

useSWRSubscriptioneventmit を利用し、初回のデータ取得は subscribe 関数内でそのまま行い、前後ページのデータ取得は eventmit によるトリガーを起点に fetch している。
https://swr.vercel.app/docs/subscription
https://github.com/azu/eventmit

import { eventmit } from "eventmit";
import { useCallback, useMemo } from "react";
import useSWRSubscription from "swr/subscription";
import { MessagesResponse } from "../../features/message/type";

const endpoint = "/api/messages";

export const useMessages = () => {
  const nextEvent = useMemo(() => eventmit<{ cursor: string }>(), []);
  const prevEvent = useMemo(() => eventmit<{ cursor: string }>(), []);

  const result = useSWRSubscription({ endpoint }, (args, { next }) => {
    fetch(args.endpoint)
      .then((res) => res.json())
      .then((d: MessagesResponse) => {
        next(null, d);
      });

    nextEvent.on((value) => {
      fetch(`${args.endpoint}?next=${value.cursor}`)
        .then((res) => res.json())
        .then((d: MessagesResponse) => {
          next(null, (prev: MessagesResponse) => {
            return {
              ...prev,
              data: [...prev.data, ...d.data],
              nextCursor: d.nextCursor,
            };
          });
        });
    });

    prevEvent.on((value) => {
      fetch(`${args.endpoint}?prev=${value.cursor}`)
        .then((res) => res.json())
        .then((d: MessagesResponse) => {
          next(null, (prev: MessagesResponse) => {
            return {
              ...prev,
              data: [...d.data, ...prev.data],
              prevCursor: d.prevCursor,
            };
          });
        });
    });

    return () => {
      nextEvent.offAll();
    };
  });

  const fetchNextPage = useCallback(
    (cursor: string) => {
      nextEvent.emit({ cursor });
    },
    [nextEvent],
  );

  const fetchPrevPage = useCallback(
    (cursor: string) => {
      prevEvent.emit({ cursor });
    },
    [prevEvent],
  );

  return { ...result, fetchNextPage, fetchPrevPage };
};

ただし useSWRSubscription の本来の用途からは少し外れている気はする。