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