SWRConfig を使うと global mutate が使えない問題を解決する
TL;DR
initCache を利用して cache と mutate のペアを新たに生成して使うと良い。
はじめに
swr は内部にインメモリキャッシュを持っており、同じ key を指定するとデータを共有する。stale-while-revalidate に由来した自動更新機能も内包しており、手軽に使えるデータフェッチライブラリである。
swr は SWRConfig を利用してインメモリキャッシュを設定することもできる。しかし、独自に設定するとグローバルミューテートが使用できなくなるのでそれを今回は解決していこうと思う。
const config = {
provider: () => new Map(),
};
const App = () => {
return (
<SWRConfig value={config}>
<Suspense>
<Component />
</Suspense>
</SWRConfig>
);
};
const getData = () => Promise.resolve({ ts: Date.now() });
const Component = () => {
const { data } = useSWR("data", getData, { suspense: true });
const handleClick = useCallback(async () => {
await mutate("data", await getData());
}, []);
return (
<div>
<p>timestamp: {data.ts}</p>
<button onClick={handleClick}>mutate</button>
</div>
);
};
swr のデフォルトのキャッシュはどこから来ている?
swr のキャッシュを知るためにどこでキャッシュを作っているかを探してみる。
これが import useSWR from 'swr' の中身。
この中で cache が利用されているところを見てみる。
この辺りがキャッシュからデータを読んでるところ
どうやら useSWRHandler の第三引数の config からキャッシュを取得しているようだ。
普段 useSWR を利用する上で config を明示的に指定することはないがここでは必須になっている。withArgs から注入されているみたいなのでそちらを見てみる。
あった。useSWRConfig から config は注入されているようだ。
useSWRConfig 内部では useContext が利用されているので、最も近い SWRConfig を探して config を返す。
swr のキャッシュは SWRConfig で provider が設定されていればそちらを、なければ defaultConfig の provider を参照することが分かった。
ここが defaultConfig 内のキャッシュを作成している場所。initCache 関数を利用して cache と mutate のペアを作成している。
キャッシュを差し替えたうえでグローバルミューテートを使う
先ほどキャッシュの在り処を調べたことで、swr が提供している mutate 関数は defaultConfig に刺さっている cache のみを向いていることが分かった。
ならば、SWRConfig に設定するキャッシュを initCache で作成し、cache と mutate のペアを新たに作りそちらを利用すれば良い。
import { initCache } from "swr/_internal";
import type { ScopedMutator, Cache } from "swr/_internal";
const [cache, mutate] = initCache(new Map()) as [Cache<unknown>, ScopedMutator];
const config = {
provider: () => cache,
};
const App = () => {
return (
<SWRConfig value={config}>
<Suspense>
<Component />
</Suspense>
</SWRConfig>
);
};
const getData = () => Promise.resolve({ ts: Date.now() });
const Component = () => {
const { data } = useSWR("data", getData, { suspense: true });
const handleClick = useCallback(async () => {
await mutate("data", await getData());
}, []);
return (
<div>
<p>timestamp: {data.ts}</p>
<button onClick={handleClick}>mutate</button>
</div>
);
};
うごいた。
一応 docs を更新する PR を出している。


Discussion