こんにちは。intdash グループ フロントエンドエンジニアの佐藤です。
Next.js を使った管理画面を作成するプロジェクトを担当する機会がありました。 管理画面は「頻繁にデータが更新されることがない」、「同時アクセスはあまり起きない」という前提の元に作成することが多いと考えています。 なのでサーバー、表示ともにパフォーマンス的に無駄なリクエストを投げたくはありません。
それを解決するため、プロジェクトにReact Query を導入しました。 今回はその導入事例をご紹介したいと思います。
SSRを使用してデータを表示する
SSR については下記のリンクが参考になります。
まずサーバーサイドはgetServerSideProps でデータを取得します。
// index.tsx const USER_KEY = "USER_KEY"; const fetchUser = async (baseUrl: string): Promise<{ name: string }> => { const res = await fetch(`${baseUrl}/api/user`); return await res.json(); }; export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({ req, }) => { const queryClient = new QueryClient(); const protocol = req.headers["x-forwarded-proto"] || "http"; const baseUrl = req ? `${protocol}://${req.headers.host}` : ""; await queryClient.prefetchQuery([USER_KEY], () => fetchUser(baseUrl)); return { props: { baseUrl, dehydratedState: dehydrate(queryClient), }, }; };
次にfetchUser
でPrefetchしたデータをUSER_KEY
のQuery に紐づけます。
dehydrate でクエリキャッシュをDehydrate し、dehydratedState を介してページにProps として渡します。
queryClient.prefetchQuery
で特徴的なのはその返り値です。
公式ドキュメントにある通りPromise<void>
を返します。エラーもスローされません。
エラーのハンドリングや取得したデータをサーバーサイドで使用する場合はqueryClient.fetchQuery
を使います。
(参考: https://github.com/TanStack/query/discussions/1494#discussioncomment-226271 )
クライアントサイドではuseQuery
を使って実装します。
const Page: React.FC<ServerSideProps> = () => { const { data } = useQuery([USER_KEY], () => fetchUser(), { staleTime: Infinity, }); return ( <div> <p>{data.name}</p> </div> ); };
useQuery
のQuery Key はサーバーサイドと同様にUSER_KEY
を指定します。
オプションにはstaleTime: Infinity
を追加しました。
このオプションによりキャッシュは常に「新鮮 (新しい)」と見なされ、バックグラウンドでの再取得が発生しません。
StaleTime やCacheTime については下記のブログがわかりやすかったです。
データを更新する
「データを表示する」でオプションにstaleTime: Infinity
を追加しました。
これによりデータの再取得は「キャッシュを無効化 (= 古くなったとみなす)」させるか「キャッシュをリセット」する必要があります。
「キャッシュの無効化」するにはqueryClient.invalidateQueries
を使います。
const updateName = React.useCallback(() => { mutate(name, { onSuccess: () => { queryClient.invalidateQueries([USER_KEY]); }, }); }, [mutate, name, queryClient]);
updateName
が実行される際に、USER_KEY
に紐づくキャッシュを無効化しました。
同じような形で「キャッシュをリセット」するにはqueryClient.resetQueries
を使用します。
この2つはメソッドを実行したときのデータの残り方に違いがあります。
queryClient.invalidateQueries
は無効化されたキャッシュが再取得完了まで残ります。
一方、queryClient.resetQueries
は再取得前にキャッシュが初期状態にリセットされるので「なにも取得していない状態」になってから再取得をします。
どちらが良いかはワークフローによると思いますが、管理画面は「更新しようとしているデータ」をわかりやすくするためにqueryClient.invalidateQueries
が良さそうです。
まとめ
React Query はAPI の状態を返してくれるだけではなく、強力なのはそのキャッシュ機構にあると考えます。 このキャッシュをうまく使いこなせれば、リクエストを減らすことができ、よりよいユーザー体験につながるアプリケーションにすることができます。