Reactで使える無限スクロールのライブラリはたくさんあれどいざインストールしてみると自身のサイト上では想定した通りに動作しないものが多かったです。
具体的には狙ったスクロール位置で読み込まれない、次のデータが読み込まれない、動作がおかしくなる。といった理由で使えるものが少なく自分で実装しようか悩みました。
原因はわからないのでそれらを yarn remove していったわけですがおそらくは扱っているコンポーネントが複雑すぎたこと、1コンポーネントの内部で計算処理を行っていたことなども関係していたかもしれません。
一方でreact-infinite-scroll-componentは普通に大きめのコンポーネントでも動いたので紹介するのとつまづいてしまったポイントも解説します。
NextJSの中で使用しています。
(更新もたまにされているライブラリ)
yarn add react-infinite-scroll-component
GitHubリポジトリ:https://github.com/ankeetmaini/react-infinite-scroll-component#readme
react-infinite-scroll-componentを使った成果物
プロフィールの Cardコンポーネント を無限に読み込むことを想定したサンプルを作りました。
(Chakra Templatesのコンポーネント使ってます)
GitHubリポジトリにはclassのサンプルがあるので constの関数型 でサンプルを作ってみています。
CodeSandBox: https://codesandbox.io/s/sample-react-infinite-scroll-component-0rfm0?file=/src/InfinityScroll.jsx
import React, { useState } from "react";
import Card from "./Card";
import InfiniteScroll from "react-infinite-scroll-component";
import { Skeleton, Box } from "@chakra-ui/react";
const InfiniteScrollComponent = () => {
const [list, setList] = useState(
Array.from({ length: 2 }, () => <Card />)
);
const fetchMoreData = () => {
//非同期っぽくするためにsetTimeoutを使っている。実際はasyncでデータフェッチしたりを想定。
setTimeout(() => {
setList([...list, Array.from({ length: 2 }, () => <Card />)]);
}, 500);
};
const loader = (
<Skeleton w="320px" m="0 auto">
<Card />
</Skeleton>
);
return (
<Box overflowY="scroll" h="420px">
<InfiniteScroll
dataLength={list.length} //現在のデータの長さ
next={fetchMoreData} // スクロール位置を監視してコールバック(次のデータを読み込ませる)
hasMore={true} // さらにスクロールするかどうか(ある一定数のデータ数に達したらfalseを返すことで無限スクロールを回避)
loader={loader} // ローディング中のコンポーネント
height={420} // 高さ(なくても良い)
>
{list}
</InfiniteScroll>
</Box>
);
};
export default InfiniteScrollComponent;
react-infinite-scroll-componentの良かったところ
・シンプル
・ちゃんと動く
・コンポーネントのrefreshオプションも実装されていて地味にありがたい
InfiniteScrollのオプションなどの解説
省略
<Box overflowY="scroll" h="420px">
<InfiniteScroll
dataLength={list.length} //現在のデータの長さ
next={fetchMoreData} // スクロール位置を監視してコールバック(次のデータを読み込ませる)
hasMore={true} // さらにスクロールするかどうか(ある一定数のデータ数に達したらfalseを返すことで無限スクロールを回避)
loader={loader} // ローディング中のコンポーネント
height={420} // 高さ(なくても良い)
>
{list}
</InfiniteScroll>
</Box>
省略
dataLength, next, hasMoreは設定はマスト。上の注釈の通りですが中身を見ていきます。
stateでデフォルト設定
const [list, setList] = useState(
Array.from({ length: 2 }, () => <Card />)
);
通常、初期ローディング時に表示させたい分だけのデータ数をここに格納しておきます。
上は length: 2 のダミー配列を作り、コールバックに () => を読んで Cardコンポーネント をstateへ直接格納しています。
(もちろんstateへ格納するデータは必ずしもコンポーネントである必要はなし)
fetchMoreData関数
const fetchMoreData = () => {
//非同期っぽくするためにsetTimeoutを使っている。実際はasyncでデータフェッチしたりを想定。
setTimeout(() => {
setList([...list, Array.from({ length: 2 }, () => <Card />)]);
}, 500);
};
setListでデータを追加しています。スプレッド構文を用いて現在の list の中身を出して、追加で再びダミー配列を2つ作って Cardコンポーネント を追加させる形でState更新します。
dataLengthでつまづいた
dataLength={list.length} としており、現在レンダリングされているデータ数をここに格納してあげます。
何を勘違いしたのか、ここに初期データ数を設定してしまったままにして、2回目以降は無限スクロールしなくなってしまってつまづきました。
heightでつまづいた
heightや参照するJSXのidを設定していない場合、windowの位置をベースにして無限スクロールされてしまいます。
自分の用途では スタイル属性に overflowY を指定していてその中で無限スクロールを実現させたかったため、windowスクロールをする度になぜか overflowY の中身も増えていく理由に悩んでつまづきました。
省略
<Box overflowY="scroll" h="420px">
<InfiniteScroll
dataLength={list.length} //現在のデータの長さ
next={fetchMoreData} // スクロール位置を監視してコールバック(次のデータを読み込ませる)
hasMore={true} // さらにスクロールするかどうか(ある一定数のデータ数に達したらfalseを返すことで無限スクロールを回避)
loader={loader} // ローディング中のコンポーネント
height={420} // 高さ(なくても良い)
>
{list}
</InfiniteScroll>
</Box>
省略
上の通り、Box(ChakraUIのdivに相当するコンポーネント) の高さ 420px にしていることもあり、InfiniteScroll の高さも合わせて height={420} に設定しました。
これを設定することで overflowY 属性のスクロールの中で無限スクロールの挙動を実装できています。
実際の使用では margin や padding なども踏まえた上で子コンポーネントの高さに調整させることを想定されます。
自分で実装せずに済んで良かった
うまく動作しないライブラリに自分なりに手を加えて時間を溶かすのも、かといって車輪の再発明も辛いのでちょうど良いNextJSでも動いたコンポーネントが見つかって良かったマン。