SWRはローカルの状態管理としても使える

3 min read読了の目安(約3200字

SWRとは

SWRとはReactのためのデータフェッチライブラリです。
詳しい説明は省略しますが、リモートのデータ取得のためのキャッシュ機構を提供するライブラリとして知られています。
この記事では、そんなSWRが実はローカルの状態管理にも使用できることを紹介します。

この記事のローカルとは、リモート(APIなど)から取得するデータの対義語として使用しており、クライアントサイドで完結する状態のことを指しています。コンポーネント内部に閉じた状態のことではないことをご了承ください。

Contextを使用する状態管理

通常は次のようにContext APIを使用することが多いでしょう。

import {
  createContext,
  ReactNode,
  VFC,
  useState,
  Dispatch,
  SetStateAction,
  useContext,
} from 'react';

type ICountContext = {
  count: number;
  setCount: Dispatch<SetStateAction<number>>;
};

const CountContext = createContext<ICountContext>({
  count: 0,
  setCount: () => {},
});

type Props = {
  children: ReactNode;
};

export const CountProvider: VFC<Props> = ({ children }) => {
  const [count, setCount] = useState<number>(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
};

export const useCountState = (): [
  ICountContext['count'],
  ICountContext['setCount']
] => {
  const { count, setCount } = useContext(CountContext);
  return [count, setCount];
};

import { AppProps } from 'next/app';
import { VFC } from 'react';
import { CountProvider } from '../context';

const MyApp: VFC<AppProps> = ({ Component, pageProps }) => {
  return (
    <CountProvider>
      <Component {...pageProps} />;
    </CountProvider>
  );
};

export default MyApp;

SWRを使用した状態管理

同様の状態管理をSWRで書くと次のようになります。

export const useSWRCountState = (
  initialCount: number
): [number, (count: number) => void] => {
  const { data: count, mutate: setCount } = useSWR('count', null, {
    initialData: initialCount,
  });
  return [count as number, setCount];
};

fetcher関数にnullを渡してmutatesetStateの関数として使用しています。
そしてオプションのinitialDataに初期値を設定します。
このようにSWRを使うことで、簡単にグローバルなuseState関数を作ることができました。
個人的にはRecoilと似たような状態管理ができて気に入っています。

ちなみに useSWR の引数には以下の4種類を渡すことができます。
参照

[cacheKey]
[cacheKey, fetcher]
[cacheKey, option]
[cacheKey, fetcher, option]

これを見るとfetcher関数にnullを渡さずに省略してもいい気がしますが、SWRはfetcher関数を省略すると次の関数がデフォルトで設定されます。https://github.com/vercel/swr/blob/11533ee8d5df6136726c4fc7a05bdbf1ac82fb35/src/libs/web-preset.ts#L22

const fetcher = (url: string) => fetch(url).then((res) => res.json());

なので、明示的にnullを渡しています。

先程のSWRを使用して簡単なサンプルコードを書いてみます。
cssは省略しています。

const SampleCount = () => {
  const { data: count } = useSWR('count', null);
  return <div>{count || 0}</div>;
};

const IndexPage: VFC = () => {
  const [count, setCount] = useSWRCountState(0);

  return (
    <>
      <div>{count}</div>
      <div>
        <SampleCount />
      </div>
      <div>
        <Button onClick={() => setCount(count + 1)}>+1</Button>
      </div>
    </>
  );
};

export default IndexPage;

問題なく動いていますね。

最後に

というわけでSWRはローカルの状態管理ライブラリとしても使用できます。
この方法は自分で思いついたわけではなく、SWRのソースコードを読んでいて知りました。

https://github.com/vercel/swr/blob/11533ee8d5df6136726c4fc7a05bdbf1ac82fb35/src/resolve-args.ts#L35

https://paco.sh/blog/shared-hook-state-with-swr