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

2021/04/22に公開3

SWRとは

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

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

Discussion

わたるわたる

貴重な情報有難うございます!
initialData: initialCountの部分でエラーがでるようになってしまったのですが、何かご存知でしょうか?
一度swrを1.0.1にアップグレードした影響かもしれません。
ダウングレードしてもエラーが消えなくなってしまいました。


Fetcher<any> | null] | readonly [Key, Partial<PublicConfiguration<any, any, Fetcher<any>>> | undefined] | readonly [...]'.
  Type '["count", null, { initialData: number; }]' is not assignable to type 'readonly [Key, Fetcher<any> | null, Partial<PublicConfiguration<any, any, Fetcher<any>>> | undefined]'.
    Type at position 2 in source is not compatible with type at position 2 in target.
      Type '{ initialData: number; }' is not assignable to type 'Partial<PublicConfiguration<any, any, Fetcher<any>>>'.
        Object literal may only specify known properties, and 'initialData' does not exist in type 'Partial<PublicConfiguration<any, any, Fetcher<any>>>'.ts(2345)

追記:

npm uninstall swr 
npm install swr@0.5.7

したところ正常動作しました。
1.0.0以上から発生するエラーの様です。