【ExpoとSupabaseで作る認証フロー (2)】invalidateQueriesでアプリの状態を自動同期する
はじめに
前回の記事では、useMutation
を使い、サインアップのようなサーバーの状態を変更するアクションを正しく実装する方法を学びました。
しかし、アクションが成功しても、アプリは「ユーザーのログイン状態が変わったこと」にまだ気づいていません。
Step 1: 「信頼できる唯一の情報源」をQueryで定義する
この問題を解決するには、まずアプリ内に「現在のログイン状態を知るための、信頼できる唯一の情報源」を定義する必要があります。その情報源として、Supabaseから現在のセッション情報を取得するuseQuery
を使ったカスタムフックを作成します。
まず、api/auth/function.ts
にセッションを取得する関数を追加します。
// ... 前回のsignUpwithEmail関数など
export async function getSession() {
const { data, error } = await supabase.auth.getSession();
if (error) throw error;
// sessionオブジェクトそのもの(またはnull)を返す
return data.session;
}
次に、このgetSession
関数をuseQuery
でラップします。これが、アプリ内のどのコンポーネントからも参照される「唯一の情報源」となります。
カスタムフックはapi/auth/index.ts
に追記します。
// ... useSignUpフックなど
import { useQuery } from '@tanstack/react-query';
import { getSession } from './function';
import { keys } from './keys'; // クエリキーを管理するファイル
export function useGetSession() {
const { data: session, isPending, isError } = useQuery({
// このQueryを['auth', 'session']というキーで管理する
queryKey: keys.session(),
queryFn: getSession,
});
return { session, isPending, isError };
}
ここで初めて登場したkeys.ts
は、TanStack Queryがキャッシュを管理するために使うキーを、一箇所でまとめて定義しているファイルです。こうすることで、キーのタイポを防ぎ、管理がしやすくなります。
export const keys = {
all: ['auth'] as const,
session: () => [...keys.all, 'session'] as const,
// ... 他のキー
} as const;
Step 2: なぜUIが更新されないのか? キャッシュの存在
useGetSession
フックは、一度取得したセッション情報をキャッシュに保存します。これは、毎回サーバーに問い合わせることなく、高速にデータを表示するためのパフォーマンス向上の仕組みです。
問題はここにあります。サインインやサインアウト(Mutation)が成功しても、useGetSession
(Query)はこの事実を知りません。そのため、古い(例えば、ログイン状態の)キャッシュデータを返し続けてしまい、UIが更新されなかったのです。
invalidateQueries
でキャッシュの鮮度を更新する
Step 3: ここで登場するのがqueryClient.invalidateQueries
です。これは、特定のクエリのキャッシュに対して「そのデータはもう古い(stale)から、次に必要になった時に再取得してね!」と合図を送る機能です。
この合図を、各種Mutationが成功したタイミング(onSuccess
)で送るように、前回の記事で作成したフックを修正します。まずは、サインインとサインアウトのfunction.ts
と、それらを使うカスタムフックをindex.ts
に追加しましょう。
// ... getSession関数など
export async function signInWithEmail({ email, password }: signInWithEmailTypes) { /* ... */ }
export async function signOut() { /* ... */ }
import { useMutation, useQueryClient } from "@tanstack/react-query";
// ...
// サインイン・サインアウト用のカスタムフックを追加
export function useSignIn() { /* ... */ }
export function useSignOut() { /* ... */ }
そして、これら全てのMutationフック(useSignUp
, useSignIn
, useSignOut
)のonSuccess
に、invalidateQueries
を追加します。
export function useSignOut() {
// 1. queryClientインスタンスを取得
const queryClient = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: signOut,
onSuccess: () => {
console.log('サインアウト成功');
// 2. 成功後、keys.session()キーを持つクエリ(useGetSession)を無効化!
queryClient.invalidateQueries({ queryKey: keys.session() });
},
onError: (error) => { /* ... */ },
});
return { mutate, isPending };
}
// useSignUp, useSignInも同様に修正する
まとめ
これで、データの流れは以下のようになります。
- ユーザーがサインアウトボタンを押す(
useSignOut
のmutate
実行)。 - Mutationが成功し、
onSuccess
が呼ばれる。 -
invalidateQueries
がuseGetSession
のキャッシュは古いとマークする。 -
useGetSession
が自動でgetSession
を再実行し、最新のセッション情報(null
)を取得する。 -
session
変数が更新され、これを参照しているUIが自動で再レンダリングされる。
MutationがQueryに「情報が古くなったよ」と合図を送る。 この連携こそが、TanStack Queryを使った状態管理の核心です。
謝辞
このシリーズの執筆にあたり、以下のZenn Bookを参考にさせていただきました。TanStack Queryの学習に非常に役立つ素晴らしい資料です。執筆者のTaiseiさんに心から感謝申し上げます。
Discussion