2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[日本語訳]SWR ドキュメンテーション ページネーション

Last updated at Posted at 2021-01-04

追記: 2021年6月8日

SWR公式日本語訳ページが追加されたので、そちらをご覧ください

このページは Pagination – SWRの日本語訳です
SWR日本語訳全体についてはSWR 日本語訳をご覧ください

ページネーション

✅ このAPIを使用するには、最新バージョン(≥0.3.0)に更新してください。 以前の useSWRPages APIは非推奨になりました。

SWRは、ページネーションインフィニティローディングなどの一般的なUIパターンをサポートするための専用API useSWRInfinite を提供します。

useSWRInfinite よりも useSWR を使用する場合

ページネーション

まず、useSWRInfinite必要ないかもしれませんが、次のような場合useSWR だけで構築できます。

スクリーンショット 2021-01-02 1.58.32.png

...これは典型的なページネーションUIです。 useSWR を使用して簡単に実装する方法を見てみましょう。


function App () {
  const [pageIndex, setPageIndex] = useState(0);

  // API URLにはReact stateのページインデックスが含まれています。
  const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);

  // ...読み込みとエラー状態を扱う

  return <div>
    {data.map(item => <div key={item.id}>{item.name}</div>)}
    <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
  </div>
}

さらに、この「ページコンポーネント」の抽象化を作成できます。


function Page ({ index }) {
  const { data } = useSWR(`/api/data?page=${index}`, fetcher);

  // ...読み込みとエラー状態を扱う

  return data.map(item => <div key={item.id}>{item.name}</div>)
}

function App () {
  const [pageIndex, setPageIndex] = useState(0);

  return <div>
    <Page index={pageIndex}/>
    <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
  </div>
}

SWRのキャッシュがあるため、次のページをプリロードできるという利点があります。 次のページを非表示のdiv内にレンダリングするため、SWRは次のページのデータフェッチをトリガーします。 ユーザーが次のページに移動すると、データはすでにそこにあります。


function App () {
  const [pageIndex, setPageIndex] = useState(0);

  return <div>
    <Page index={pageIndex}/>
    <div style={{ display: 'none' }}><Page index={pageIndex + 1}/></div>
    <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
  </div>
}

たった1行のコードで、はるかに優れたUXを実現できます。useSWR hookは非常に強力であるため、ほとんどの場合に対応できます。

インフィニティローディング

"Load More" ボタンを使用して、リストにデータを追加する(またはスクロールすると自動的に実行される)インフィニティローディング UIを構築したい場合があります。

スクリーンショット 2021-01-02 2.12.24.png

これを実装するには、このページで動的な数のリクエストを行う必要があります。 React Hooksにはいくつかのルールがあるため、次のようなことはできません


function App () {
  const [cnt, setCnt] = useState(1)

  const list = []
  for (let i = 0; i < cnt; i++) {
    // 🚨 これは間違い! 一般的に、hooks の中でループは使用できません
    const { data } = useSWR(`/api/data?page=${i}`)
    list.push(data)
  }

  return <div>
    {list.map((data, i) => 
      <div key={i}>{
        data.map(item => <div key={item.id}>{item.name}</div>)
      }</div>)}
    <button onClick={() => setCnt(cnt + 1)}>Load More</button>
  </div>
}

代わりに、作成した <Page /> 抽象化を使用してそれを実現できます。


function App () {
  const [cnt, setCnt] = useState(1)

  const pages = []
  for (let i = 0; i < cnt; i++) {
    pages.push(<Page index={i} key={i} />)
  }

  return <div>
    {pages}
    <button onClick={() => setCnt(cnt + 1)}>Load More</button>
  </div>
}

また、ページネーションAPIがカーソルベースの場合、その方法も機能しません。 各ページには前のページのデータが必要ですが、それは含まれていません。

その場合に、新しい useSWRInfinite フックが役立ちます。

useSWRInfinite

useSWRInfinite を使用すると、1つのフックで多数のリクエストをトリガーできます。


const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)

useSWR と同様に、この新しいフックは、リクエストキーを返す関数、フェッチャー関数、およびオプションを受け入れます。 そして、useSWR が返す全ての値と2つの追加の値、ページサイズと(React stateのようなページ)サイズセッターを返します。

インフィニティローディングでは、1 ページ が1つのリクエストであり、私たちの目標は複数のページをフェッチしてレンダリングすることです。

API

パラメータ

  • getKey: インデックスと前のページのデータを受け入れ、ページのキーを返す関数
  • fetcher: useSWRと同様の fetcher関数
  • options: useSWR でサポートされる全てのオプションと以下の3つのオプション
    • initialSize = 1: 最初にロードする必要があるページ数
    • revalidateAll = false: 常に全てのページを再検証
    • persistSize = false: 最初のページのキーが変更されたときに、ページサイズを1(また、設定されている場合はinitialSize)にリセットしない

💡 initialSize オプションはライフサイクル中に変更できないことに注意してください。

返り値

  • data: 各ページのフェッチレスポンスデータの配列
  • error: useSWRerror と同様
  • isValidating: useSWRisValidating と同様
  • mutate: useSWRにバインドされたmutate関数と同じですが、データ配列を操作します
  • size: フェッチされて返されるページ数
  • setSize: フェッチする必要のあるページ数を設定

例 1: インデックスに基づくページネーションAPI

通常のインデックスに基づくAPI用


GET /users?page=0&limit=10
[
  { name: 'Alice', ... },
  { name: 'Bob', ... },
  { name: 'Cathy', ... },
  ...
]

// 各ページのSWRキーを取得する関数であり、その戻り値は `fetcher`によって受け入れられます。
// `null`が返された場合、そのページのリクエストは開始されません。
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // 最後まで到達
  return `/users?page=${pageIndex}&limit=10`                    // SWR key
}
function App () {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
  if (!data) return 'loading'

  // すべてのユーザーの数を計算できます
  let totalUsers = 0
  for (let i = 0; i < data.length; i++) {
    totalUsers += data[i].length
  }

  return <div>
    <p>{totalUsers} users listed</p>
    {data.map((users, index) => {
      // `data` は配列で各ページのAPIレスポンスです
      return users.map(user => <div key={user.id}>{user.name}</div>)
    })}
    <button onClick={() => setSize(size + 1)}>Load More</button>
  </div>
}

getKey 関数は、useSWRInfiniteuseSWR の主な違いです。 現在のページのインデックスと前のページのデータを受け入れます。 したがって、インデックスベースとカーソルベースの両方のページネーションAPIを適切にサポートできます。

また、data は1つのAPI応答だけではなくなりました。 これは、複数のAPI応答の配列です。

// `data` はこのようになります
[
  [
    { name: 'Alice', ... },
    { name: 'Bob', ... },
    { name: 'Cathy', ... },
    ...
  ],
  [
    { name: 'John', ... },
    { name: 'Paul', ... },
    { name: 'George', ... },
    ...
  ],
  ...
]

例 2: カーソルまたはオフセットベースのページネーションAPI

APIがカーソルを必要とし、データと一緒に次のカーソルを返すとしましょう。


GET /users?cursor=123&limit=10
{
  data: [
    { name: 'Alice' },
    { name: 'Bob' },
    { name: 'Cathy' },
    ...
  ],
  nextCursor: 456
}

getKey 関数を次のように変更します。


const getKey = (pageIndex, previousPageData) => {
  // 最後まで到達
  if (previousPageData && !previousPageData.data) return null

  // 最初のページでは `previousPageData` は不要
  if (pageIndex === 0) return `/users?limit=10`

  // APIエンドポイントにカーソルを追加
  return `/users?cursor=${previousPageData.nextCursor}&limit=10`
}

高度な機能

ここにある例は、useSWRInfinite を使用して次の機能を実装する方法を示しています。

  • ローディング状態
  • 空の場合に特別なUIを表示
  • 最後まで到達した場合に「さらに読み込む」ボタンを無効化
  • 変更可能なデータソース
  • リスト全体の更新
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?