SWRの2.0がリリースされていたので新機能を学ぶ

# はじめに
[SWR](https://swr.vercel.app/ja)とはReactのデータ取得を快適に行うためのAPIを提供するライブラリです。
2022/12/09にメジャーバージョンである2.0がリリースされました。それを記念してこの記事では2.0によって追加された新機能を紹介します。
SWRが提供する移行ガイドは[こちら](https://swr.vercel.app/blog/swr-v2)です。

# useSWRMutation
SWR2.0では`useSWRMutation`という新しいhooksを利用できるようになりました。`useSWR`のようなhooksが呼び出されたタイミングで発火するのではなく任意のタイミングで発火させることができます。`useSWRMutation`は以下のように書いて利用します。
```ts
import useSWRMutation from 'swr/mutation'

const { data, error, trigger, reset, isMutating } =
  useSWRMutation(key, fetcher, options);
```
返り値も引数も様々ありますね。実際にサンプルを作って動作を確認していきます。
## 基本
例としてToDoリストを扱うアプリを考えます。一覧は`useSWR`を用いてこのように取得します。
```ts
const { tasks } = useSWR('/api/tasks', getTasks);
```
これまではタスクを追加する処理のような更新処理を扱うときは`mutate`を用いて
```tsx
const [text, setText] = useState('');
const [isMutating, setIsMutating] = useState(false);

return (
  <>
    <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
    <button
      type="button"
      onClick={() => {
        setIsMutating(true);
        mutate('/api/tasks', postTask(text)).finally(() => {        
          setIsMutating(false);
        });
      }}
      disabled={isMutating}
    >
      追加
    </button>
  </>
);
```
と書いていました。useSWRMutation`を用いると下のように書くことができます。
```tsx
const { trigger, isMutation } = useSWR('/api/tasks', postTask);
const [text, setText] = useState('');

return (
  <>
    <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
    <button type="button" onClick={() => trigger(text)} disabled={isMutating}>追加</button>
  </>
);
```
更新中の状態を表す`isMutation`を取得できるようになったことなど、hooksにしたことでより宣言的(React like)な書き方になり見通しが非常に良くなりました。
`mutation`と`useSWRMutation`どちらの`key`もタスク一覧を取得するAPIと同様の`key`を与えたので、更新の完了と同時にタスク一覧を読み直してくれます。ミューテーションを起こす関数の`key`と同名の`key`をもつものが再読み込みしてくれるわけです。

## 楽観的UI更新
先ほど説明した更新後のデータの読み直しですが、更新処理とデータの読み込みを待つ必要があるのでアプリを利用しているユーザーは更新後しばらく古い状態のまま利用しなければいけないです。これはユーザー体験として優れていません。
SWRでは更新処理とデータの読み込みが終わるまでの代わりのデータを与えることでUI上では新しい状態が表示されるような機能の提供を始めました。この機能は`useSWRMutation`だけでなく`mutate`でも利用できます。
この機能は本来実装が大変ですが、SWRで提供される機能では以下のように簡単に実装することができます。
```tsx
const { trigger, isMutation } = useSWR('/api/tasks', postTask);
const [text, setText] = useState('');

return (
  <>
    <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
    <button
      type="button"
      onClick={() => (
      trigger(
        text,
        {
          optimisticData: (tasks) => [...tasks, newTask(text)],
        }
      )
      disabled={isMutating}
    }>
      追加
    </button>
  </>
);
```
`newTask`は新しいタスクを作る関数です。`trigger`関数の第二引数である`options`の`optimisticData`に更新前のデータを受け取って更新した結果を返す関数を置くことで`isMutation`の間はその仮のデータを表示するようになります。大変簡単で便利なので助かります。`mutate`の場合も同じように第三引数である`options`の`optimisticData`で設定することができます。
このような手法を用いた場合は更新処理が失敗した時に元の状態に戻す必要があります。`options`の`rollbackOnError`を`true`に設定すると定義した更新処理がrejectされたときに自動で元の状態に戻ります。
他にも`populateCache`を設定することで更新処理の結果をミューテーション予定のデータとすることができます。これによってデータ取得APIの読み込みを防ぐことができます(このとき再検証を防ぐために`revalidate`はfalseにする必要があります)。

## 複数キーのミューテーション
これまでは`mutate`の`key`に`useSWR`の`key`と同じ引数しか渡せなかったので、ミューテーションされるキーは一つでした。SWR2.0ではフィルタ関数と呼ばれる関数を渡すことで複数のキーをミューテーションすることができます。`tasks`を含む全てのキーを更新する場合は以下のように書きます。
```ts
mutate((key: string) => /^(?=.*task).*$/.test(key), updateTask);
```
これを利用して
```ts
mutate(() => true, undefined, { revalidate: false });
```
のようにすれば全てのキーをリセットすることも可能です。


## これらを利用したサンプル
どのAPIも0.5秒後に実行されるようにしたサンプルを作成しました。追加や更新も0.5秒かかりますが、楽観的UI更新によってそれを感じさせません。`trigger`を行うだけでデータの読み込みも自動でできていることも`useSWRMutation`があってこそです。

https://codesandbox.io/embed/swr2-0-useswrmutation-1-knh29b?fontsize=14&hidenavigation=1&module=%2Ftasks%2FTaskList.tsx&theme=dark

このデモで`Invalid or unexpected token browser`こんな感じのエラーが出てる場合は×を押して使ってください。これが出る原因がわからないのですが、ローカル環境では問題なく動くのでcodesandbox起因じゃないかと考えています。原因を知っている方いれば教えてください。

# preload
`preload`を使うことで事前にデータを読み込むことができます。
```ts
preload('/api/tasks', getTasks);
```
Reactの外、グローバルスコープで呼ぶこともできますのでコンポーネントが呼ばれる段階にはデータの準備を完了させて読み込みの隙をなくすことができます(完了していなくても途中までは読み込めているので素早く表示できます)。
Reactの中でも遷移先が決まっているときは先に読み込んであげたり、決まっていなくてもホバーやフォーカスなどのタイミングで読み込んであげたりすることで遷移後の読み込み時間が削減され良いユーザー体験を提供することができます。
`preload`によって読み込みを行なっているときに、同キーを`useSWR`などで読み込む場合は`preload`の読み込みを待ってそれによる結果を利用します。`prelaod`の呼び出しが棄却されるわけではないので安心して使えます。

# isLoading
`useSWR`の返り値に新しく`isLoading`が追加されました。初期データを読み込んでいるの状態の時にのみ`true`になる値です。
これまでは`isValidating`を利用して`isValidating`が`true`の時にデータとエラーが存在しない時を定義する必要がありました。
```ts
const { data, error, isValidating } = useSWR('/api/tasks', getTasks);

const isLoading = !(data && error) && isValidating;
```
記述がめんどくさかったので助かりますね。`isLoading`が台頭したことで`isValidating`が不要になるわけではありません。初期ローディング状態とデータを持っているときのローディング状態で出し分けが可能だからです。例えば初期ではコンポーネント全体にスピナーを出すようにして、それ以外はデータを持っているのでデータの横にスピナーを小さく出すようなことができます。

# keepPreviousData
新しく追加されたオプションです。`key`が変化するような読み込みを行うときに、`key`ごとの結果を保持しておくような設定を行うことができます。検索を`onChange`のタイミングで行う時などに有効です。タスクを検索する例を見てみます。
```tsx
const [text, setText] = useState('');
const { data: task } = useSWR(
  `/api/tasks?name=${text}`,
  getTask,
  {
    keepPreviousData: true
  }
);

return (
  <>
    <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
    <Task task={task} />
  </>
);
```
この例だと`hello world`と検索したい時に`h`で`/api/tasks?name=h`、`he`で`/api/tasks?name=he`と一文字ずつ取得します。そしてそれぞれの結果を保存して持っています。`hello world`と検索した後に`hello`の結果を見ようとすると`hello world`を見る過程であらかじめ取得しているのですぐにデータを見ることができます。

# SWRConfigの継承
SWRConfigを親でも設定している場合それを引き継ぐことが可能になりました。
```tsx
<SWRConfig
  value={parentConfig => ({
    ...parentConfig,
    suspense: true,
  })}
>
  {children}
</SWRConfig>
```
これまでテスト環境などで同じ設定が使えるように別ファイルに`config`を設定していたのですが、この機能によってその必要は無くなりました。

# さいごに
メジャーバージョンアップなだけはあってかなり有用な機能が多かった印象です。mutate周りはもう少しだけ機能が欲しいなあと思っていたのでたくさん追加されてとても満足です。他にも変更や開発者ツールの紹介があったので確認すると面白いと思います。