Reactで外部API(JSON)から取得したデータで、次のデータと前のデータを表示するために、ページネーションを自作する方法を紹介します。
今回は、ポケモンAPIで実際に取得して表示します。
Reactのページネーションは、ライブラリを使わなくても「現在のページのデータ」「次ページのURL」「前ページのURL」の3つをuseStateで持ち、ボタンのクリックで次(前)のURLをfetchしてstateを入れ替えるだけで作れます。ここで扱うのは、APIのレスポンスにnext・previousのURLが含まれるタイプです。ページ番号を自分で管理するタイプ(?page=2やoffsetで指定)でも、stateに持つものがURLからページ番号に変わるだけで考え方は同じです。
Reactでページネーションを自作する方法
ファイル構成
src
∟App.js
∟index.js
∟components
∟Card
∟Card.js
∟utils
get.js
App.jsでループして外部API(JSON)を取得した分だけCardを作成する構成とします。
utils/get.jsには、URLを受け取ってJSONを返す関数を置きます。以降のコードで使うgetAllSomeはこの関数です。
// utils/get.js
export const getAllSome = async (url) => {
const res = await fetch(url);
return res.json();
};
loadSomeは、一覧のresultsからCardに必要な詳細データを取得してsetSomeDataに渡す関数です。APIによっては一覧に必要な情報がすべて入っているので、その場合はsetSomeData(res.results)だけで済みます。
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
上でAppを読み込みます。
index.jsは特にいじるところはありません。
App.js
import { useEffect, useState } from "react";
import Card from "./components/Card/Card";
function App() {
// 外部API
const initialURL = "https://endpoint"; //endpoint
const [loading, setLoading] = useState(true); //最初にloading出したいのでtrue
const [someData, setSomeData] = useState([]);
const [nextURL, setNextURL] = useState(""); //次の20件のURLの状態
const [prevURL, setPrevURL] = useState(""); //前の20件のURLの状態
useEffect(() => {
const fetchSomeData = async () => {
// すべてのポケモンデータを取得
let res = await getAllSome(initialURL);
// console.log(res);
// console.log(res.next); //次の20件がある
// console.log(res.previous); //前の20件がある
// 詳細データを取得
loadSome(res.results);
setNextURL(res.next);
setPrevURL(res.previous);
setLoading(false);
};
fetchSomeData();
}, []);
const handlePrevPage = async () => {
if(!prevURL) return; //1ページ目は前のデータはないので、returnしておく
setLoading(true);
let data = await getAllSome(prevURL);
await loadSome(data.results);
setNextURL(data.next);
setPrevURL(data.previous);
setLoading(false);
// console.log(data);
};
const handleNextPage = async () => {
if(!nextURL) return;
setLoading(true);
let data = await getAllSome(nextURL);
await loadSome(data.results);
setNextURL(data.next); //3ページ目以降のデータを取得するために状態を変更
setPrevURL(data.previous); //2ページ目以降で前へのデータを取得する
setLoading(false);
// console.log(data);
};
return (
<>
<div className="App">
{loading ? (
<p className="loader">ロード中。。。</p>
) : (
<>
<div>
{someData.map((some, i) => {
return <Card key={i} some={some} />;
})}
</div>
<div className="btn">
<button onClick={handlePrevPage}>前へ</button>
<button onClick={handleNextPage}>次へ</button>
</div>
</>
)}
</div>
</>
);
}
export default App;
外部APIから取得して、Cardコンポーネントをmapを使ってデータを表示しています。keyの付け方やmapで表示されないときの原因はReactで配列を表示する方法|map・filter・keyと表示されない原因にまとめています。読み込み中の表示をスピナーにしたい場合は【React】ローディング(スピナー)を表示する方法を参考にしてください。
ページネーション部分のみを説明します。
<div className="btn">
<button onClick={handlePrevPage}>前へ</button>
<button onClick={handleNextPage}>次へ</button>
</div>
JSX部分で、ボタンを用意します。
そのボタンにはそれぞれ前のデータを取得するhandlePrevPage関数と次のデータを取得するhandleNextPage関数を設定しています。
const [nextURL, setNextURL] = useState(""); //次の20件のURLの状態
const [prevURL, setPrevURL] = useState(""); //前の20件のURLの状態
前のページと次のページは現在のページで変わるため、状態管理をします。
const handlePrevPage = async () => {
if(!prevURL) return; //1ページ目は前のデータはないので、returnしておく
setLoading(true);
let data = await getAllSome(prevURL);
await loadSome(data.results);
setNextURL(data.next);
setPrevURL(data.previous);
setLoading(false);
// console.log(data);
};
まず、jsonに次のデータと前のデータが格納されているプロパティがあるはずなので、それを探します。上の場合は、data.nextとdata.previousというプロパティがある場合です。
handlePrevPage関数とhandleNextPage関数はほぼ同じなので、handlePrevPage関数だけ説明すると、取得したデータをロードしたら、それぞれの更新用関数に取得した次と前のデータをセットします。
そうすることで、常に現在のページの次と前のデータが保持されます。
あとは、前へボタン、次へボタンが押されたタイミングで、handlePrevPage関数とhandleNextPage関数を発火させれば完成です。初回のデータはuseEffectで取得しています。
うまく動かないとき
「前へ」「次へ」を押しても何も起きない場合は、data.nextやdata.previousのプロパティ名がAPIのレスポンスと一致しているか、ブラウザの開発者ツールのNetworkタブで実際のJSONを確認します。最終ページでnextがnullになるAPIなら、if(!nextURL) return;のガードで止まるので、ボタン自体をdisabled={!nextURL}にすると押せないことが見た目で伝わります。
また、React 18の開発モードではStrictModeによってuseEffectが2回実行され、初回のfetchが2回飛びます。本番ビルドでは1回になるので、開発中に2回リクエストが見えても不具合ではありません。