ミューテーションと再検証
SWRは、リモートデータと関連キャッシュをミューテーションするためのmutate
APIとuseSWRMutation
APIを提供します。
mutate
データをミューテーションするためにmutate
APIを使用するには、任意のキーをミューテーションできるグローバルミューテートAPIと、対応するSWRフックのデータのみをミューテーションできるバウンドミューテートAPIの2つの方法があります。
グローバルミューテート
グローバルミューテータを取得するための推奨される方法は、useSWRConfig
フックを使用することです。
import { useSWRConfig } from "swr"
function App() {
const { mutate } = useSWRConfig()
mutate(key, data, options)
}
グローバルにインポートすることもできます。
import { mutate } from "swr"
function App() {
mutate(key, data, options)
}
key
パラメータのみでグローバルミューテータを使用すると、同じキーを使用するマウントされたSWRフックがない限り、* **キャッシュの更新や再検証のトリガーは行われません***。
バウンドミューテート
バウンドミューテートは、現在のキーをデータでミューテーションするためのショートパスです。key
は、useSWR
に渡されるkey
にバインドされ、最初の引数としてdata
を受け取ります。
これは、前のセクションのグローバルmutate
関数と機能的に同等ですが、key
パラメータは必要ありません。
import useSWR from 'swr'
function Profile () {
const { data, mutate } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
// send a request to the API to update the data
await requestUpdateUsername(newName)
// update the local data immediately and revalidate (refetch)
// NOTE: key is not required when using useSWR's mutate as it's pre-bound
mutate({ ...data, name: newName })
}}>Uppercase my name!</button>
</div>
)
}
再検証
データなしでmutate(key)
(またはバウンドミューテートAPIでmutate()
のみ)を呼び出すと、リソースの再検証(データを期限切れとしてマークし、再フェッチをトリガーする)がトリガーされます。この例は、ユーザーが「ログアウト」ボタンをクリックしたときに、ログイン情報(例:<Profile/>
内)を自動的に再フェッチする方法を示しています。
import useSWR, { useSWRConfig } from 'swr'
function App () {
const { mutate } = useSWRConfig()
return (
<div>
<Profile />
<button onClick={() => {
// set the cookie as expired
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
// tell all SWRs with this key to revalidate
mutate('/api/user')
}}>
Logout
</button>
</div>
)
}
これは、同じキャッシュプロバイダスコープの下にあるSWRフックにブロードキャストされます。キャッシュプロバイダが存在しない場合は、すべてのSWRフックにブロードキャストされます。
API
パラメータ
key
:useSWR
のkey
と同じですが、関数はフィルター関数として動作します。data
:クライアントキャッシュを更新するデータ、またはリモートミューテーションのための非同期関数。options
:次のオプションを受け入れます。optimisticData
:クライアントキャッシュをすぐに更新するデータ、または現在のデータを受け取り、新しいクライアントキャッシュデータを返す関数。通常、楽観的UIで使用されます。revalidate = true
:非同期更新が解決したら、キャッシュを再検証する必要があるかどうか。関数に設定されている場合、関数はdata
とkey
を受け取ります。populateCache = true
:リモートミューテーションの結果をキャッシュに書き込む必要があるかどうか、または新しい結果と現在の結果を引数として受け取り、ミューテーション結果を返す関数。rollbackOnError = true
:リモートミューテーションがエラーになった場合、キャッシュをロールバックするかどうか。または、fetcher からスローされたエラーを引数として受け取り、ロールバックするかどうかをブール値で返す関数。throwOnError = true
:失敗した場合、mutate 呼び出しがエラーをスローするかどうか。
戻り値
mutate
は、data
パラメータが解決された結果を返します。 mutate
に渡された関数は、対応するキャッシュ値の更新に使用される更新されたデータを返します。関数の実行中にエラーがスローされた場合、エラーは適切に処理できるようにスローされます。
try {
const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
// Handle an error while updating the user here
}
useSWRMutation
SWR は、リモートミューテーション用のフックとして useSWRMutation
も提供します。リモートミューテーションは、useSWR
のように自動的にトリガーされるのではなく、手動でトリガーされます。
また、このフックは他の useSWRMutation
フックと状態を共有しません。
import useSWRMutation from 'swr/mutation'
// Fetcher implementation.
// The extra argument will be passed via the `arg` property of the 2nd parameter.
// In the example below, `arg` will be `'my_token'`
async function updateUser(url, { arg }: { arg: string }) {
await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${arg}`
}
})
}
function Profile() {
// A useSWR + mutate like API, but it will not start the request automatically.
const { trigger } = useSWRMutation('/api/user', updateUser, options)
return <button onClick={() => {
// Trigger `updateUser` with a specific argument.
trigger('my_token')
}}>Update User</button>
}
API
パラメータ
key
:mutate
のkey
と同じです。fetcher(key, { arg })
:リモートミューテーション用の非同期関数options
:以下のプロパティを持つオプションのオブジェクトoptimisticData
:mutate
のoptimisticData
と同じです。revalidate = true
:mutate
のrevalidate
と同じです。populateCache = false
:mutate
のpopulateCache
と同じですが、デフォルトはfalse
です。rollbackOnError = true
:mutate
のrollbackOnError
と同じです。throwOnError = true
:mutate
のthrowOnError
と同じです。onSuccess(data, key, config)
:リモートミューテーションが正常に完了した場合のコールバック関数onError(err, key, config)
:リモートミューテーションがエラーを返した場合のコールバック関数
戻り値
data
:fetcher
から返された、指定されたキーのデータerror
:fetcher
によってスローされたエラー(または undefined)trigger(arg, options)
:リモートミューテーションをトリガーする関数reset
:状態(data
、error
、isMutating
)をリセットする関数isMutating
:進行中のリモートミューテーションがあるかどうか
基本的な使い方
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }: { arg: { username: string }}) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
}).then(res => res.json())
}
function App() {
const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */)
return (
<button
disabled={isMutating}
onClick={async () => {
try {
const result = await trigger({ username: 'johndoe' }, /* options */)
} catch (e) {
// error handling
}
}}
>
Create User
</button>
)
}
ミューテーションの結果をレンダリングで使用したい場合は、useSWRMutation
の戻り値から取得できます。
const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)
useSWRMutation
は useSWR
とキャッシュストアを共有するため、useSWR
との間の競合状態を検出して回避できます。また、楽観的更新やエラー時のロールバックなど、mutate
の機能もサポートしています。これらのオプションは、useSWRMutation
とその trigger
関数に渡すことができます。
const { trigger } = useSWRMutation('/api/user', updateUser, {
optimisticData: current => ({ ...current, name: newName })
})
// or
trigger(newName, {
optimisticData: current => ({ ...current, name: newName })
})
必要な場合までデータの読み込みを遅延する
データの読み込みには、useSWRMutation
も使用できます。useSWRMutation
は、trigger
が呼び出されるまでリクエストを開始しないため、実際に必要なときにデータの読み込みを遅延させることができます。
import { useState } from 'react'
import useSWRMutation from 'swr/mutation'
const fetcher = url => fetch(url).then(res => res.json())
const Page = () => {
const [show, setShow] = useState(false)
// data is undefined until trigger is called
const { data: user, trigger } = useSWRMutation('/api/user', fetcher);
return (
<div>
<button onClick={() => {
trigger();
setShow(true);
}}>Show User</button>
{show && user ? <div>{user.name}</div> : null}
</div>
);
}
楽観的更新
多くの場合、データにローカルで変更を適用することで、データソースからの応答を待つことなく、変更を高速に反映できます。
optimisticData
オプションを使用すると、リモートでの変更処理が完了するのを待つ間に、ローカルデータを手動で更新できます。rollbackOnError
を組み合わせることで、データのロールバックを制御することもできます。
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
const user = { ...data, name: newName }
const options = {
optimisticData: user,
rollbackOnError(error) {
// If it's timeout abort error, don't rollback
return error.name !== 'AbortError'
},
}
// updates the local data immediately
// send a request to update the data
// triggers a revalidation (refetch) to make sure our local data is correct
mutate('/api/user', updateFn(user), options);
}}>Uppercase my name!</button>
</div>
)
}
updateFn
は、リモートの変更処理を行うためのPromiseまたは非同期関数で、更新されたデータを返す必要があります。
現在のデータに依存させるために、optimisticData
に関数を渡すこともできます。
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
mutate('/api/user', updateUserName(newName), {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
});
}}>Uppercase my name!</button>
</div>
)
}
useSWRMutation
と trigger
を使用して同じことを実現することもできます。
import useSWRMutation from 'swr/mutation'
function Profile () {
const { trigger } = useSWRMutation('/api/user', updateUserName)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
trigger(newName, {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
})
}}>Uppercase my name!</button>
</div>
)
}
エラー時のロールバック
optimisticData
を設定している場合、楽観的データがユーザーに表示されるものの、リモートの変更が失敗する可能性があります。この場合、rollbackOnError
を活用してローカルキャッシュを以前の状態に戻し、ユーザーに正しいデータが表示されるようにすることができます。
変更後のキャッシュの更新
リモートの変更リクエストが更新されたデータを直接返す場合があり、その場合は追加のフェッチを行って読み込む必要はありません。populateCache
オプションを有効にすることで、変更のレスポンスを使用して useSWR
のキャッシュを更新できます。
const updateTodo = () => fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
mutate('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
// Since the API already gives us the updated information,
// we don't need to revalidate here.
revalidate: false
})
または、useSWRMutation
フックを使用します。
useSWRMutation('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
// Since the API already gives us the updated information,
// we don't need to revalidate here.
revalidate: false
})
optimisticData
と rollbackOnError
と組み合わせることで、完璧な楽観的UIエクスペリエンスを実現できます。
競合状態の回避
mutate
と useSWRMutation
はどちらも、useSWR
間の競合状態を回避できます。例:
function Profile() {
const { data } = useSWR('/api/user', getUser, { revalidateInterval: 3000 })
const { trigger } = useSWRMutation('/api/user', updateUser)
return <>
{data ? data.username : null}
<button onClick={() => trigger()}>Update User</button>
</>
}
通常の useSWR
フックは、フォーカス、ポーリング、またはその他の条件により、いつでもデータをリフレッシュする可能性があります。これにより、表示されるユーザー名は可能な限り最新の状態になります。ただし、useSWR
の再フェッチとほぼ同時に発生する可能性のある変更があるため、getUser
リクエストが先に開始されるが、updateUser
よりも長くかかるという競合状態が発生する可能性があります。
幸いなことに、useSWRMutation
はこれを自動的に処理します。変更後、useSWR
に進行中のリクエストを破棄して再検証するように指示するため、古いデータが表示されることはありません。
現在のデータに基づく変更
現在のデータに基づいてデータの一部を更新したい場合があります。
mutate
では、キャッシュされている現在の値(存在する場合)を受け取り、更新されたドキュメントを返す非同期関数を渡すことができます。
mutate('/api/todos', async todos => {
// let's update the todo with ID `1` to be completed,
// this API returns the updated data
const updatedTodo = await fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
// Since the API already gives us the updated information,
// we don't need to revalidate here.
}, { revalidate: false })
複数項目の変更
グローバル mutate
API は、引数として key
を受け取り、再検証するキーを返すフィルター関数を受け入れます。フィルター関数は、既存のすべてのキャッシュキーに適用されます。
import { mutate } from 'swr'
// Or from the hook if you customized the cache provider:
// { mutate } = useSWRConfig()
mutate(
key => typeof key === 'string' && key.startsWith('/api/item?id='),
undefined,
{ revalidate: true }
)
これは、配列などのキータイプでも機能します。変更は、最初の要素が 'item'
であるすべてのキーに一致します。
useSWR(['item', 123], ...)
useSWR(['item', 124], ...)
useSWR(['item', 125], ...)
mutate(
key => Array.isArray(key) && key[0] === 'item',
undefined,
{ revalidate: false }
)
フィルター関数は既存のすべてのキャッシュキーに適用されるため、複数の形状のキーを使用する場合、キーの形状を想定しないでください。
// ✅ matching array key
mutate((key) => key[0].startsWith('/api'), data)
// ✅ matching string key
mutate((key) => typeof key === 'string' && key.startsWith('/api'), data)
// ❌ ERROR: mutate uncertain keys (array or string)
mutate((key: any) => /\/api/.test(key.toString()))
フィルター関数を使用してすべてのキャッシュデータをクリアできます。これは、ログアウト時に役立ちます。
const clearCache = () => mutate(
() => true,
undefined,
{ revalidate: false }
)
// ...clear cache on logout
clearCache()