SWRを使用する際のTips

2021/12/23に公開

はじめに

私事ですが、今年はフロントエンドの領域(「Next.js x Typescript」)をメインに開発した1年でした。
その中で、SWRを使うことが多く、今回はSWRを使用する際のTipsを簡単にご紹介できればと思います。
https://swr.vercel.app/

この記事で話すこと

  • SWRを使用する際のTips
    • SWRのカスタムフックを作る
    • SWRを状態管理に利用する

この記事で話さないこと

  • SWRについての細かな説明
  • API仕様の説明

※SWRに関しては本家以外では下記記事が非常に参考になります。
(本当にお世話になった記事です🙇‍♂️)
https://zenn.dev/uttk/articles/b3bcbedbc1fd00

Tips1: SWRのカスタムフックを作る

通常、SWRを使用する際はこんな感じで使用すると思います。

import useSWR from 'swr'

function Hoge () {
  const { data, error } = useSWR('/hoge/1', fetcher);

  if (error) return <div>Error</div>
  if (!data) return <div>Loading...</div>

  return <div>{data}</div>
}

どんな問題が起きるのか?

私がよく直面した問題としては、2つのfetcherに対して、異なるkey名で1つ1つのfetcherを分けて管理したいにもかかわらず、誤って同一の名を書いてしまい、値の取得ができないなどのバグが発生しました。

import useSWR from 'swr'

function Hoge () {
  const { data, error } = useSWR('/hoge/1', fetcherHoge);

  if (error) return <div>Error</div>
  if (!data) return <div>Loading...</div>

  return <div>{data}</div>
}

function Fuga () {
  // fetcherFugaに対しても'/hoge/1'というkey名を割り当ててしまっている
  const { data, error } = useSWR('/hoge/1', fetcherFuga);

  // ...
}

そして、こいつが厄介なのが、レビューでは見逃しやすいことです。
また、当初はどこがバグっているのかも見つけづらく大変だった記憶があります...

SWRのカスタムフックを作る

上記問題を解決するために、useSWRをラップしたカスタムフックを作ることにしました。
こうすることで、カスタムフック内にkey名が内包されるので、誤ってkey名を書くことを防ぐことができます。
※実際にはもう少し共通化できる部分があると思いますが、説明の都合上、愚直に実装しています。

カスタムフックの例

import useSWR from 'swr'

export interface IFetchHoge {
  data: IHoge | undefined;
  error: Error | undefined;
}
export const useFetchHoge = (): IFetchHoge => {
  const { data, error } = useSWR('/hoge/1', fetcherHoge);

  return { data, error };
};

export interface IFetchFuga {
  data: IFuga | undefined;
  error: Error | undefined;
}
export const useFetchFuga = (): IFetchFuga => {
  const { data, error } = useSWR('/fuga/1', fetcherFuga);

  return { data, error };
};

このカスタムフックを使用する側

function Hoge () {
  const { data, error } = useFetchHoge();

  if (error) return <div>Error</div>
  if (!data) return <div>Loading...</div>

  return <div>{data}</div>
}

function Fuga () {
  const { data, error } = useFetchFuga();

  // ...
}

また、副次的な効果として使用するComponen側がSWRに一切依存しなくなるので、将来的にSWRを他のライブラリに変える際もカスタムフック内を修正するだけで良くなります!

Tips2: SWRを状態管理に利用する

SWRを状態管理に使用することも個人的にはやってよかったかなと思うことの一つです。

メリットとしては

  • mutateを使用して、バックエンド側に問い合わせてから状態の更新を行うので、データの整合性が取れる
  • useSWRConfig#mutateは各Componentでmutate関数を呼べば良いので、無駄なpropsのバケツリレーが減る

デメリットとしては

  • 毎回データを問い合わせるので、通信が多くなってしまう
    • ※リフェッチせず、ローカルの値だけ変更する方法もあります
    • mutate() の第二引数に false渡すなど

カスタムフックの例

useHogeQuery.ts
import useSWR, { useSWRConfig } from "swr";

export interface IFetchHoge {
  data: IHoge | undefined;
  error: Error | undefined;
  mutate: () => void;
}

// useSWR用のカスタムフック
export const useFetchHoge = (): IFetchHoge => {
  const { data, error, mutate } = useSWR('/hoge/1', fetcherHoge);

  return { data, error, mutate };
};


export interface IRefetchHoge {
  mutate: () => void;
}

// useSWRConfigのmutate関数用のカスタムフック
export const useRefetchHoge = (): IRefetchHoge => {
  const { mutate: refetchMutate } = useSWRConfig();

  const mutate = () =>
    refetchMutate('/hoge/1');

  return { mutate };
};

このカスタムフックを使用する側

  • 状態を取得する時
function Hoge () {
    const { data, error } = useFetchHoge();

  if (error) return <div>Error</div>
  if (!data) return <div>Loading...</div>

  return <div>{data}</div>
}
  • 状態を更新する時(useSWRのmutate関数を使用する場合)
function Hoge () {
    const { data, error, mutate } = useFetchHoge();

    return (
      <div>
        <button onClick={() => mutate({ ...data, name: newName })}>
          Click!!!
        </button>
      </div>
    )
}
  • 状態を更新する時(useSWRConfigのmutate関数を使用する場合)
function Hoge () {
    const { mutate } = useRefetchHoge();

    return (
      <div>
        <button onClick={mutate}>
          Click!!!
        </button>
      </div>
    )
}

まとめ

以上、簡単ではありますがSWRを使用する際のTipsを紹介させていただきました。
まだまだ、SWRへの理解が足りていないところもあるので、引き続きキャッチアップしていき、他に良いTipsがあれば共有したいと思います!

Discussion