SWR ver 2.0 の Optimistic Updates (楽観的 UI 更新)について

こんにちは、システムエンジニアの Kota です。今回は少し前に発表された SWR ver 2.0 に便利で新しいオプションが追加されたので、簡単に解説したいと思います。

今回追加されたオプション

  • optimisticData
    • クライアントキャッシュを即座に更新するためのデータ、または現在のデータを受け取り新しいクライアントキャッシュデータを返す関数
  • populateCache
    • リモートミューテーションの結果をキャッシュに書き込むかどうか、またはリモートミューテーションの結果と現在のデータを引数として受け取り、ミューテーションの結果を返す関数
  • rollbackOnError
    • リモートミューテーションがエラーだった場合にキャッシュをロールバックするかどうか、または発生したエラーを引数として受け取りロールバックするかどうかの真偽値を返す関数

公式のドキュメントは こちら です。
公式のブログは こちら です。

解説

公式ブログでは、簡単な Todo の追加が例として記載されています。

await addNewTodo('New Item')

addNewTodo は, Todo リストに新しい Todo を追加するための Promise もしくは、非同期関数で、更新後のデータを返します。
これまでは、Todo が追加された場合、 UI への表示は、 mutate を通してサーバにリクエストを送信し、リモートで最新のデータを取得し、返ってきたレスポンス内のデータを使って UI 側を更新するといった流れでした。

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* データの表示 */}</ul>

  <button onClick={async () => {
    await addNewTodo('New Item')
    mutate()
  }}>
    Add
  </button>
</>


※ 画面は後述する公式の example

問題点

もし仮に await addNewTodo('New Item') のリクエストで時間が掛かった場合、レスポンスが返ってくるまでの間、更新前の Todo リストが表示されたままの状態になり UX が低下してしまいます。

新オプション

今回 ver 2.0 で登場した新しいオプション optimisticData でそれを解決できます。

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* データの表示 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      // クライアントキャッシュを即座に更新する
      optimisticData: [...data, 'New Item'],
    })
  }}>
    Add
  </button>
</>

上記のように optimisticData オプションを使うことで、サーバからのレスポンスを待つことなく新しい状態のリストを表示できます。dataoptimisticData の値によって更新し、サーバにリクエストを送信します。リクエストが完了したら SWR はリソースを revalidate しデータが最新であることを保証します。
また、populateCache オプションを有効にすることで、ミューテーションのレスポンスで useSWR のキャッシュを更新できます。

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* データの表示 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      // ミューテーションのレスポンスで useSWR のキャッシュを更新
      populateCache: true,
    })
  }}>
    Add
  </button>
</>

populateCache オプションを有効にした場合、レスポンスのデータが正しい場合、revalidate は必要ないので、合わせて無効化します。

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* データの表示 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      // populateCache を有効にする場合は、revalidate は不要
      revalidate: false,
    })
  }}>
    Add
  </button>
</>

addNewTodo が例外で失敗した場合は、楽観的な更新によるデータがユーザーに表示されます。正しいデータを保証する為に、rollbackOnError オプションで、optimisticData のデータを更新前の data にロールバックします。( default で rollbackOnError=true になっている )


const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* データの表示 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
      rollbackOnError: true,
    })
  }}>
    Add
  </button>
</>

参考

  • 公式の example でオプションの設定を変えてみて、挙動を確認してみて下さい。

まとめ

いかがでしたか? 今回は SWR ver 2.0 で登場した新しいオプションについて解説してみました。なお、本日ご紹介したオプションは同じく 2.0 で登場した useSWRMutation でも使えるオプションになりますので、合わせてチェックしてみて下さい。