無限スクロールの実装

前回SWRを導入したが、件数が少ないうちは良いのだが、件数が増えてくるとロード時間が長くなる問題が発生する。
という訳で今回はページングを実装したい。

SWR公式をよく読んでみるとページングはサポートされているようで、
useSWRではなくuseSWRInfiniteを使用すれば良さそう。

react-infinite-scrollerコンポーネントを利用すれば無限スクロールを簡単に実装できるようだ。
 
npm install swr --save
npm install react-infinite-scroller --save
BlogPostsList.tsx
import firebase from 'firebase'
import flamelink from 'flamelink/app'
import 'flamelink/content'
import { useSWRInfinite } from "swr";
import InfiniteScroll from "react-infinite-scroller"

export default function BlogPostsList(props: any) {
    const textLength = 100;
    const getPostFunc: any = (url: any, lastPost: any) => {
        return getPosts(lastPost)
    };
    const { data, error, size, setSize } = useSWRInfinite(
        (index, previousPageData) => {
            let lastPage = undefined;
            if (previousPageData && previousPageData.length > 0) {
                lastPage = previousPageData[previousPageData.length - 1];
            }
            return [props.key + '/' + (index + 1), lastPage];
        },
        getPostFunc, 
        { initialData: [props.initialData] }  //初期データ。配列で渡す
    );
    if (error) return <div>failed to load</div>
    if (!data) return <div>loading...</div>
    const postData = data ? [].concat(...data) : [];
    const isLoadingInitialData = !data && !error;
    const isLoadingMore =
        isLoadingInitialData ||
        (size > 0 && data && typeof data[size - 1] === "undefined");
    const isEmpty = data?.[0]?.length === 0;
    const isReachingEnd =
        isEmpty || (data && data[data.length - 1]?.length < props.pageSize);
    const loadMore = () => {
        if (!isLoadingMore && !isReachingEnd) {
            setSize(size + 1);
        }
    };

    return (
        <InfiniteScroll
            loadMore={loadMore}    //項目を読み込む際に処理するコールバック関数
            hasMore={!isReachingEnd}      //読み込みを行うかどうかの判定*/}
            loader={<>loading...</>}>      {/* 読み込み最中に表示する項目 */}
            {postData.map(post => <div>{post.title}</div>)}       {/* 無限スクロールで表示する項目 */}
        </InfiniteScroll>
    )
}

export const getStaticProps: GetStaticProps = async () => {
    const posts = await getPosts();

    return {
        props: {
            posts: posts
        }
    }
}

const getPosts: any = async (lastPost?: any) => {
    const getOptions: any = { schemaKey: "myposts", populate: true, filters: [['_fl_meta_.status', '==', 'publish']], orderBy: [{ field: 'like', order: 'desc' }, 'id'], limit: PAGE_PER_CONTENTS };
    if (lastPost && lastPost.like !== undefined && lastPost.id !== undefined) {
        getOptions.startAfter = [lastPost.like, lastPost.id];
    }

    const _posts = await app.content.get(getOptions);
    const posts: any[] = [];
    if (_posts) {
        Object.keys(_posts).forEach(key => {
            const post = _posts[key]
            delete (post._fl_meta_);
            posts.push(post)
        });
    }
    return posts;
}
const PAGE_PER_CONTENTS = 10
const firebaseApp = firebase;
const app = flamelink({
    firebaseApp,
    dbType: "cf",
});
useSWRInfinitはページの配列で返ってくるので、 initialDataはページの配列で渡してやるのがハマりどころだった。