コンテンツにスキップ
ドキュメント
ミューテーションと再検証

ミューテーションと再検証

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

パラメータ

  • keyuseSWRkeyと同じですが、関数はフィルター関数として動作します。
  • data:クライアントキャッシュを更新するデータ、またはリモートミューテーションのための非同期関数。
  • options:次のオプションを受け入れます。
    • optimisticData:クライアントキャッシュをすぐに更新するデータ、または現在のデータを受け取り、新しいクライアントキャッシュデータを返す関数。通常、楽観的UIで使用されます。
    • revalidate = true:非同期更新が解決したら、キャッシュを再検証する必要があるかどうか。関数に設定されている場合、関数はdatakeyを受け取ります。
    • 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

パラメータ

  • keymutatekey と同じです。
  • fetcher(key, { arg }):リモートミューテーション用の非同期関数
  • options:以下のプロパティを持つオプションのオブジェクト
    • optimisticDatamutateoptimisticData と同じです。
    • revalidate = truemutaterevalidate と同じです。
    • populateCache = falsemutatepopulateCache と同じですが、デフォルトは false です。
    • rollbackOnError = truemutaterollbackOnError と同じです。
    • throwOnError = truemutatethrowOnError と同じです。
    • onSuccess(data, key, config):リモートミューテーションが正常に完了した場合のコールバック関数
    • onError(err, key, config):リモートミューテーションがエラーを返した場合のコールバック関数

戻り値

  • datafetcher から返された、指定されたキーのデータ
  • errorfetcher によってスローされたエラー(または undefined)
  • trigger(arg, options):リモートミューテーションをトリガーする関数
  • reset:状態(dataerrorisMutating)をリセットする関数
  • 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)

useSWRMutationuseSWR とキャッシュストアを共有するため、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>
  )
}

useSWRMutationtrigger を使用して同じことを実現することもできます。

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
})

optimisticDatarollbackOnError と組み合わせることで、完璧な楽観的UIエクスペリエンスを実現できます。

競合状態の回避

mutateuseSWRMutation はどちらも、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()