😎

【ExpoとSupabaseで作る認証フロー (2)】invalidateQueriesでアプリの状態を自動同期する

に公開

はじめに

前回の記事では、useMutationを使い、サインアップのようなサーバーの状態を変更するアクションを正しく実装する方法を学びました。

しかし、アクションが成功しても、アプリは「ユーザーのログイン状態が変わったこと」にまだ気づいていません。

Step 1: 「信頼できる唯一の情報源」をQueryで定義する

この問題を解決するには、まずアプリ内に「現在のログイン状態を知るための、信頼できる唯一の情報源」を定義する必要があります。その情報源として、Supabaseから現在のセッション情報を取得するuseQueryを使ったカスタムフックを作成します。

まず、api/auth/function.tsにセッションを取得する関数を追加します。

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に追記します。

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がキャッシュを管理するために使うキーを、一箇所でまとめて定義しているファイルです。こうすることで、キーのタイポを防ぎ、管理がしやすくなります。

api/auth/keys.ts
export const keys = {
  all: ['auth'] as const,
  session: () => [...keys.all, 'session'] as const,
  // ... 他のキー
} as const;

Step 2: なぜUIが更新されないのか? キャッシュの存在

useGetSessionフックは、一度取得したセッション情報をキャッシュに保存します。これは、毎回サーバーに問い合わせることなく、高速にデータを表示するためのパフォーマンス向上の仕組みです。

問題はここにあります。サインインやサインアウト(Mutation)が成功しても、useGetSession(Query)はこの事実を知りません。そのため、古い(例えば、ログイン状態の)キャッシュデータを返し続けてしまい、UIが更新されなかったのです。

Step 3: invalidateQueriesでキャッシュの鮮度を更新する

ここで登場するのがqueryClient.invalidateQueriesです。これは、特定のクエリのキャッシュに対して「そのデータはもう古い(stale)から、次に必要になった時に再取得してね!」と合図を送る機能です。

この合図を、各種Mutationが成功したタイミング(onSuccess)で送るように、前回の記事で作成したフックを修正します。まずは、サインインとサインアウトのfunction.tsと、それらを使うカスタムフックをindex.tsに追加しましょう。

api/auth/function.ts
// ... getSession関数など
export async function signInWithEmail({ email, password }: signInWithEmailTypes) { /* ... */ }
export async function signOut() { /* ... */ }
api/auth/index.ts
import { useMutation, useQueryClient } from "@tanstack/react-query";
// ...

// サインイン・サインアウト用のカスタムフックを追加
export function useSignIn() { /* ... */ }
export function useSignOut() { /* ... */ }

そして、これら全てのMutationフック(useSignUp, useSignIn, useSignOut)のonSuccessに、invalidateQueriesを追加します。

api/auth/index.ts (修正後)
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も同様に修正する

まとめ

これで、データの流れは以下のようになります。

  1. ユーザーがサインアウトボタンを押す(useSignOutmutate実行)。
  2. Mutationが成功し、onSuccessが呼ばれる。
  3. invalidateQueriesuseGetSessionのキャッシュは古いとマークする。
  4. useGetSessionが自動でgetSessionを再実行し、最新のセッション情報(null)を取得する。
  5. session変数が更新され、これを参照しているUIが自動で再レンダリングされる。

MutationがQueryに「情報が古くなったよ」と合図を送る。 この連携こそが、TanStack Queryを使った状態管理の核心です。

謝辞

このシリーズの執筆にあたり、以下のZenn Bookを参考にさせていただきました。TanStack Queryの学習に非常に役立つ素晴らしい資料です。執筆者のTaiseiさんに心から感謝申し上げます。

立命館慶祥高等学校【K-Tech】

Discussion