ページネーション
このAPIを使用するには、最新バージョン(≥ 0.3.0)に更新してください。以前のuseSWRPages
APIは非推奨になりました。
SWRは、ページネーションや無限ローディングなどの一般的なUIパターンをサポートする専用のAPI useSWRInfinite
を提供します。
useSWR
を使用する場合
ページネーション
まず、次のようなものを構築している場合は、useSWRInfinite
を使用する必要はなく、useSWR
だけで十分です。
…これは典型的なページネーションUIです。useSWR
で簡単に実装する方法を見てみましょう。
function App () {
const [pageIndex, setPageIndex] = useState(0);
// The API URL includes the page index, which is a React state.
const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);
// ... handle loading and error states
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);
// ... handle loading and error states
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
フックは非常に強力であるため、ほとんどのシナリオをカバーできます。
無限ローディング
「もっと読み込む」ボタンでリストにデータを追加する(またはスクロール時に自動的に行われる)無限ローディングUIを構築したい場合があります。
これを実装するには、このページで動的な数のリクエストを行う必要があります。React Hooksにはいくつかのルール (新しいタブで開きます)があるため、次のようなことはできません。
function App () {
const [cnt, setCnt] = useState(1)
const list = []
for (let i = 0; i < cnt; i++) {
// 🚨 This is wrong! Commonly, you can't use hooks inside a loop.
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>
}
高度なケース
ただし、一部の高度なユースケースでは、上記の解決策は機能しません。
たとえば、同じ「もっと読み込む」UIを実装していますが、合計でいくつの項目があるかを示す数も表示する必要があります。トップレベルのUI(<App />
)が各ページ内のデータを必要とするため、<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>
<p>??? items</p>
{pages}
<button onClick={() => setCnt(cnt + 1)}>Load More</button>
</div>
}
また、ページネーションAPIがカーソルベースの場合も、その解決策は機能しません。各ページは前のページのデータを必要とするため、隔離されていません。
これが、新しいuseSWRInfinite
フックが役立つ理由です。
useSWRInfinite
useSWRInfinite
は、1つのフックで複数のリクエストをトリガーする機能を提供します。その方法は次のとおりです。
import useSWRInfinite from 'swr/infinite'
// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
getKey, fetcher?, options?
)
useSWR
と同様に、この新しいフックは、リクエストキーを返す関数、フェッチャー関数、およびオプションを受け入れます。また、useSWR
が返す値すべてと、2つの追加の値(ページサイズとReactステートのようなページサイズセッター)を返します。
無限ローディングでは、1つのページが1つのリクエストであり、私たちの目標は複数のページをフェッチしてレンダリングすることです。
SWR 0.xバージョンを使用している場合は、useSWRInfinite
をswr
からインポートする必要があります。
import { useSWRInfinite } from 'swr'
API
パラメーター
getKey
: インデックスと前のページデータを受け取り、ページのキーを返す関数fetcher
:useSWR
のフェッチャー関数と同じoptions
:useSWR
がサポートするすべてのオプションに加えて、4つの追加オプションを受け付けます。initialSize = 1
: 初期ロードされるページ数revalidateAll = false
: 常にすべてのページの再検証を試みます。revalidateFirstPage = true
: 常に最初のページの再検証を試みます。persistSize = false
: 最初のページのキーが変更されたときに、ページサイズを1(または設定されている場合はinitialSize
)にリセットしません。parallel = false
: 複数のページを並行してフェッチします。
initialSize
オプションは、ライフサイクル中に変更することはできません。
戻り値
data
: 各ページのフェッチレスポンス値の配列error
:useSWR
のerror
と同じisLoading
:useSWR
のisLoading
と同じisValidating
:useSWR
のisValidating
と同じmutate
:useSWR
のバインドされた mutate 関数と同じですが、データ配列を操作します。size
: フェッチされ、返される予定のページ数setSize
: フェッチする必要のあるページ数を設定します。
例1: インデックスベースのページネーションAPI
通常のインデックスベースのAPIの場合
GET /users?page=0&limit=10
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
]
// A function to get the SWR key of each page,
// its return value will be accepted by `fetcher`.
// If `null` is returned, the request of that page won't start.
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null // reached the end
return `/users?page=${pageIndex}&limit=10` // SWR key
}
function App () {
const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
if (!data) return 'loading'
// We can now calculate the number of all users
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` is an array of each page's API response.
return users.map(user => <div key={user.id}>{user.name}</div>)
})}
<button onClick={() => setSize(size + 1)}>Load More</button>
</div>
}
getKey
関数は、useSWRInfinite
と useSWR
の主な違いです。現在のページのインデックスと、前のページのデータを受け取ります。そのため、インデックスベースとカーソルベースのページネーションAPIの両方が適切にサポートできます。
また、data
は、もはや1つのAPIレスポンスではありません。複数のAPIレスポンスの配列です。
// `data` will look like this
[
[
{ 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) => {
// reached the end
if (previousPageData && !previousPageData.data) return null
// first page, we don't have `previousPageData`
if (pageIndex === 0) return `/users?limit=10`
// add the cursor to the API endpoint
return `/users?cursor=${previousPageData.nextCursor}&limit=10`
}
並列フェッチモード
このAPIを使用するには、最新バージョン(≥ 2.1.0)に更新してください。
useSWRInfinite のデフォルトの動作は、キーの作成が以前にフェッチされたデータに基づいているため、各ページのデータを順番にフェッチすることです。ただし、特にページが相互に依存していない場合、多数のページを順番にフェッチすることは最適ではない可能性があります。parallel
オプションを true
に指定すると、ページを並行して個別にフェッチできるようになり、ロードプロセスを大幅に高速化できます。
// parallel = false (default)
// page1 ===> page2 ===> page3 ===> done
//
// parallel = true
// page1 ==> done
// page2 =====> done
// page3 ===> done
//
// previousPageData is always `null`
const getKey = (pageIndex, previousPageData) => {
return `/users?page=${pageIndex}&limit=10`
}
function App () {
const { data } = useSWRInfinite(getKey, fetcher, { parallel: true })
}
getKey
関数の previousPageData
引数は、parallel
オプションを有効にすると null
になります。
特定のページの再検証
このAPIを使用するには、最新バージョン(≥ 2.2.5)に更新してください。
useSWRInfinite
のミューテーションのデフォルトの動作は、ロードされたすべてのページを再検証することです。しかし、変更された特定のページのみを再検証したい場合があります。revalidate
オプションに関数を渡すことで、特定のページのみを再検証できます。
revalidate
関数は、各ページに対して呼び出されます。
function App() {
const { data, mutate, size } = useSWRInfinite(
(index) => [`/api/?page=${index + 1}`, index + 1],
fetcher
);
mutate(data, {
// only revalidate the last page
revalidate: (pageData, [url, page]) => page === size
});
}
useSWRInfinite
を使用したグローバルミューテート
useSWRInfinite
は、すべてのページデータを各ページデータとともに特別なキャッシュキーを使用してキャッシュに格納するため、グローバルなミューテートでデータを再検証するには、swr/infinite
の unstable_serialize
を使用する必要があります。
import { useSWRConfig } from "swr"
import { unstable_serialize } from "swr/infinite"
function App() {
const { mutate } = useSWRConfig()
mutate(unstable_serialize(getKey))
}
名前が示すように、unstable_serialize
は安定したAPIではないため、将来変更する可能性があります。
高度な機能
こちらに例があります。useSWRInfinite
を使用して次の機能を実装する方法を示しています。
- ローディング状態
- 空の場合に特別なUIを表示
- 最後まで到達した場合、「さらにロード」ボタンを無効化
- 変更可能なデータソース
- リスト全体の更新