Skip to content
ドキュメント
ミューテーションと再検証

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

SWR はリモートデータとキャッシュデータのミューテーションのために、mutateuseSWRMutation の API を提供しています。

mutate

mutate API を使いデータをミューテートするには 2 つの方法があります。どんなキーに対してもミューテートできるグローバルのミューテート API と対応する SWR フックのデータのみミューテートできるバウンドミューテート API です。

グローバルミューテート

グローバルなミューテートを取得するオススメの方法は useSWRConfig フックを使うことです。

import { useSWRConfig } from "swr"
 
function App() {
  const { mutate } = useSWRConfig()
  mutate(key, data, options)
}

またはグローバルにインポートすることもできます。

import { mutate } from "swr"
 
function App() {
  mutate(key, data, options)
}
⚠️

Using global mutator only with the key parameter will not update the cache or trigger revalidation unless there is a mounted SWR hook using the same 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()
        // データを更新するために API にリクエストを送信します
        await requestUpdateUsername(newName)
        // ローカルのデータを即座に更新して再検証(再フェッチ)します
        // 注意: useSWR の mutate は key が対応付けられているため、key の指定は必要ありません
        mutate({ ...data, name: newName })
      }}>Uppercase my name!</button>
    </div>
  )
}

再検証

mutate(key) (または単にバウンドミューテートの mutate()) をデータの指定なしに呼んだ場合、そのリソースに対して再検証を発行 (データを期限切れとしてマークして再フェッチを発行する) します。 この例は、ユーザーが "Logout" ボタンをクリックした場合に、 ログイン情報 (例: <Profile/> の中身) をどのように自動的に再フェッチするかを示しています。

import useSWR, { useSWRConfig } from 'swr'
 
function App () {
  const { mutate } = useSWRConfig()
 
  return (
    <div>
      <Profile />
      <button onClick={() => {
        // クッキーを期限切れとして設定します
        document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
 
        // この key を使っている全ての SWR フックに再検証を伝えます
        mutate('/api/user')
      }}>
        Logout
      </button>
    </div>
  )
}
💡

これは同じスコープの キャッシュプロバイダ 内の SWR フックに対して再検証の指示を一斉送信します。もしキャッシュプロバイダが存在しない場合、全ての SWR フックに対して一斉送信されます。

API

パラメータ

  • key: useSWRkey と同じです。しかしながら、関数は フィルタ関数 として振る舞います
  • data: クライアントキャッシュを更新するためのデータ、またはリモートミューテーションのための非同期関数
  • options: 下記のオプションを受け取ります
    • optimisticData: クライアントキャッシュを即座に更新するためのデータ、または現在のデータを受け取り新しいクライアントキャッシュデータを返す関数。楽観的 UI のために使われます
    • revalidate = true: 非同期の更新処理を完了した後にキャッシュの再検証を行うかどうか
    • populateCache = true: リモートミューテーションの結果をキャッシュに書き込むかどうか、またはリモートミューテーションの結果と現在のデータを引数として受け取り、ミューテーションの結果を返す関数
    • rollbackOnError = true: リモートミューテーションがエラーだった場合にキャッシュをロールバックするかどうか、または発生したエラーを引数として受け取りロールバックするかどうかの真偽値を返す関数
    • throwOnError = true: ミューテートの呼び出しが失敗した場合にエラーを投げるかどうか

返り値

mutatedata パラメータとして扱われる結果を返します。mutate に渡された関数は対応するキャッシュの値としても使われる更新後のデータを返します。 もし、関数の実行中にエラーが発生した場合、そのエラーはスローされるので適切に処理できます。

try {
  const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
  // ユーザーの更新中に発生したエラーを処理します
}

useSWRMutation

加えて SWR はリモートミューテーションのために useSWRMutation というフックも提供しています。このリモートミューテーションは、自動的にミューテーションを行う useSWR などとは異なり手動でのみ発行されます。

また、このフックは他の useSWRMutation と状態を共有しません。

import useSWRMutation from 'swr/mutation'
 
// フェッチャーの実装
// 追加の引数は第二引数の `arg` プロパティとして渡されます
// 下記の例では、`arg` は `'my_token'` となります
async function updateUser(url, { arg }: { arg: string }) {
  await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${arg}`
    }
  })
}
 
function Profile() {
  // useSWR と mutate を組み合わせたような API ですが、リクエストを自動的に開始しません
  const { trigger } = useSWRMutation('/api/user', updateUser, options)
 
  return <button onClick={() => {
    // `updateUser` を指定した引数と一緒に実行します
    trigger('my_token')
  }}>Update User</button>
}

API

パラメータ

  • key: mutatekey と同様
  • fetcher(key, { arg }): リモートミューテーションのための非同期関数
  • options: 下記のプロパティを持つオプションのオブジェクト
    • optimisticData: mutateoptimisticData と同様
    • revalidate = true: mutaterevalidate と同様
    • populateCache = false: mutatepopulateCache と同様、ただしデフォルト値は false
    • rollbackOnError = true: mutaterollbackOnError と同様
    • throwOnError = true: mutatethrowOnError と同様
    • 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) {
          // エラーハンドリング
        }
      }}
    >
      Create User
    </button>
  )
}

もし、ミューテーションの結果をレンダリングで使いたい場合、useSWRMutation の返り値から取得できます。

const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)

useSWRMutation はキャッシュを useSWR と共有します。そのため、useSWR とのレースコンディションを検出し回避できます。また、楽観的更新やエラー時のロールバックのような mutate が持っている機能もサポートしています。これらのオプションは useSWRMutationtrigger 関数に渡すことができます。

const { trigger } = useSWRMutation('/api/user', updateUser, {
  optimisticData: current => ({ ...current, name: newName })
})
 
// または
 
trigger(newName, {
  optimisticData: current => ({ ...current, name: newName })
})

必要になるまでデータの読み込みを遅延させる

useSWRMutation をデータのローディングのために使うこともできます。useSWRMutationtrigger が呼ばれるまでリクエストを開始しません。そのため、データが実際に必要になるまでデータの読み込みを遅延することができます。

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)
  // trigger が呼ばれるまで data は undefined
  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) {
            // タイムアウトの AbortError だった場合はロールバックしません
            return error.name !== 'AbortError'
          },
        }
 
        // ローカルのデータを即座に更新します
        // データを更新するためにリクエストを送信します
        // ローカルデータが正しいことを保証するために再検証 (再フェッチ) を発行します
        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) => {
    // リストをフィルタリングして更新後のアイテムと一緒に返します
    const filteredTodos = todos.filter(todo => todo.id !== '1')
    return [...filteredTodos, updatedTodo]
  },
  // API が更新後の情報を返してくれているので、
  // 再検証は必要ありません
  revalidate: false
})

useSWRMutation フックでも使えます。

useSWRMutation('/api/todos', updateTodo, {
  populateCache: (updatedTodo, todos) => {
    // リストをフィルタリングして更新後のアイテムと一緒に返します
    const filteredTodos = todos.filter(todo => todo.id !== '1')
    return [...filteredTodos, updatedTodo]
  },
  // API が更新後の情報を返してくれているので、
  // 再検証は必要ありません
  revalidate: false
})

optimisticDatarollbackOnError を組み合わせることで、完璧な楽観的 UI 更新の体験を得ることができます。

レースコンディションを避ける

mutateuseSWRMutationuseSWR とのレースコンディションを避けることができます。例えば、

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 => {
  // ID が `1` の TODO を完了に更新しましょう
  // この API は更新後のデータを返します
  const updatedTodo = await fetch('/api/todos/1', {
    method: 'PATCH',
    body: JSON.stringify({ completed: true })
  })
 
  // リストをフィルタリングして、更新後のデータを返します
  const filteredTodos = todos.filter(todo => todo.id !== '1')
  return [...filteredTodos, updatedTodo]
// API が更新後の情報を返してくれているので、
// 再検証は必要ありません
}, { revalidate: false })

複数のアイテムをミューテートする

グローバルの mutate API はフィルタ関数を受け取ります。この関数は key を引数として受け取り再検証するかどうかを返します。フィルタ関数は全ての存在するキャッシュキーに対して適用されます。

import { mutate } from 'swr'
// キャッシュプロバイダをカスタマイズしている場合はフックから
// { 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 }
)

フィルタ関数は全ての存在するキャッシュキーに対して適用されます。そのため複数の形式のキーを扱っている場合、キャッシュの形式に対して思い込みを持ってはいけません。

// ✅ 配列のキーに対してマッチします
mutate((key) => key[0].startsWith('/api'), data)
// ✅ 文字列のキーに対してマッチします
mutate((key) => typeof key === 'string' && key.startsWith('/api'), data)
 
// ❌ エラー: 不確かなキー (配列または文字列) に対してミューテートします
mutate((key: any) => /\/api/.test(key.toString()))

フィルタ関数を全てのキャッシュデータを消去するためにも使えます、これはログアウトした場合などに便利です。

const clearCache = () => mutate(
  () => true,
  undefined,
  { revalidate: false }
)
 
// ...ログアウト時にキャッシュをクリアします
clearCache()