Intersection Observer APIを使って無限スクロールをReactでつくってみた
はじめに
一番下までスクロールされたら次のコンテンツを読み込んで表示する無限スクロールをライブラリを使わずにIntersection Observer APIを利用して実装したのでその記録を書きました。
記事用のコードなのでjsonplaceholderからデータを取得しています。
loadingやerror時の処理は今回は省略しています...(・・)
カスタムフックでロジック部分の分離はしてません...(・・)
動作
こんな感じです。ScrollObserverコンポーネントはわかりやすく赤色の背景にしていて、こいつが交差したら関数が実行されるイメージです。
全体のコード
import { useCallback, useEffect, useState } from "react";
import "./App.css";
import { ScrollObserver } from "./ScrollObserver";
function App() {
const [todos, setTodos] = useState([]);
const [isActiveObserver, setIsActiveObserver] = useState(true);
const fetchTodos = useCallback(async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/todos?_limit=10"
);
const json = await res.json();
setTodos(json);
}, []);
useEffect(() => {
fetchTodos();
}, [fetchTodos]);
const fetchNextTodos = useCallback(async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos?_start=${todos.length}&_limit=10`
);
const json = await res.json();
// データをすべて取得したとき
if (json.length === 0) {
return setIsActiveObserver(false);
}
setTodos([...todos, ...json]);
}, [todos]);
return (
<div className="App">
<h1>無限スクロール</h1>
<div className="container">
<ol>
{todos.map((todo) => {
return <li key={todo.id}>{todo.title}</li>;
})}
</ol>
<ScrollObserver
onIntersect={fetchNextTodos}
isActiveObserver={isActiveObserver}
/>
</div>
</div>
);
}
export default App;
import { memo, useEffect, useRef } from "react";
export const ScrollObserver = memo((props) => {
const { onIntersect, isActiveObserver } = props;
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries, observer) => {
if (entries[0].intersectionRatio >= 1) {
observer.disconnect();
onIntersect();
}
},
{
threshold: 1,
}
);
observer.observe(ref.current);
}, [onIntersect]);
return (
<>
{isActiveObserver ? (
<div ref={ref} style={{ height: "50px", backgroundColor: "red" }}>
<p>読み込み中...</p>
</div>
) : null}
</>
);
});
コードの解説
初期データの取得
まずはこちらで最初にデータを10件取得しています。
const fetchTodos = useCallback(async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/todos?_limit=10"
);
const json = await res.json();
setTodos(json);
}, []);
useEffect(() => {
fetchTodos();
}, [fetchTodos]);
交差時実行したい関数作成
こちらはScrollObserverコンポーネントにわたす関数で交差時にしてほしい処理です。todosの配列の最後から10件取得するようにクエリパラメーターを書いています。依存配列にtodosがあるのはtodosの値が変わるとtodos.lengthの長さも変わるので、メモ化した値を再計算しています。取得するデータがなくなってもScrollObserverの監視状態が続いてしまっていたので、データを取得したらisActiveObserverをfalseにしてScrollObserverコンポーネントの返り値をnullにしています。この辺もうすこし簡潔に書けないか気になるのでアドバイスあればコメントしてほしいです。
const fetchNextTodos = useCallback(async () => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos?_start=${todos.length}&_limit=10`
);
const json = await res.json();
// データをすべて取得したとき
if (json.length === 0) {
return setIsActiveObserver(false);
}
setTodos([...todos, ...json]);
}, [todos]);
ScrollObserverコンポーネント
こちらはpropsで交差時に実行したい関数(今回だとfetchNextTodos)とbooleanのisActiveObserverを受け取っています。
useRefはオブザーバーを作成した後、監視するターゲット要素を与える必要があるので使用しています。useRefの詳細は公式ドキュメントを参考にしてください。
交差時の処理としてはIntersectionObserverの第一引数でおこなっており、ずっと監視しているとonIntersect()関数が何度もよばれるためobserver.disconnect()で一度監視をやめてます。onIntersectを依存配列にいれているためそちらの関数が更新時(この例ではtodosの値が代わったとき)にもう一度監視している状態にしています。第2引数は交差関連のoptionです。
useEffect(() => {
if (ref.current === null) return;
const observer = new IntersectionObserver(
(entries, observer) => {
if (entries[0].intersectionRatio >= 1) {
observer.disconnect();
onIntersect();
}
},
{
threshold: 1,
}
);
observer.observe(ref.current);
}, [onIntersect]);
こちらはisActiveObserverがtrueのとき(まだ取得できるtodoが残っているとき)、交差すると交差時の処理が行われる要素を入れ、isActiveObserverがfalseのときはnullを返しています。
return (
<>
{isActiveObserver ? (
<div ref={ref} style={{ height: "50px", backgroundColor: "red" }}>
<p>読み込み中...</p>
</div>
) : null}
</>
);
おわりに
今回は初めてIntersection Observer APIを利用して無限スクロールの実装をしたので、不出来なところがあるかもしれませんが参考になれば幸いです!間違っているところやもっとよくなるところがあればコメントお願いします(_ _)
Author And Source
この問題について(Intersection Observer APIを使って無限スクロールをReactでつくってみた), 我々は、より多くの情報をここで見つけました https://zenn.dev/tsuxxx/articles/e09ab353492943著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol