はじめに
スケジュールを立てる能力を育てるアプリを開発しました。
ログインが面を実装したのだが、それの実装に四苦八苦したので、備忘録として書いておきます。
使用ファイル
ログインに使っているファイルは以下です。
-
src/components/pages/Login.tsx
ログイン画面本体。フォーム入力と loginUser 呼び出しを担当 -
src/supabase/supabaseFunction/supabaseFunction.tsx
loginUser と getSession を定義。Supabase認証の実処理 -
src/supabase/supabase.tsx
Supabaseクライアント生成 -
src/hooks/AuthContext.tsx
セッション取得、認証状態監視、useAuth を提供 -
src/router/Router.tsx
/login ルート定義 -
src/router/ProtectedRoute.tsx
未ログイン時に /login へリダイレクト -
src/main.tsx
AuthProvider でアプリ全体をラップ -
src/components/ui/AppHeader.tsx
ログアウト処理 (signOut) と /login への遷移
設定
-
.env
VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY を定義
解説
src/components/pages/Login.tsx
ログイン画面の本体です。コンポーネント化を行なっており、こちらにHTMLなどを記載してます。実際のコードは以下
以下重要な部分をピックアップしました。
型の宣言
type LoginProps = {
email?: string;
password?: string;
};
typeを使って、メールアドレス、パスワードの型を指定します。型は全てstring型になります。
コンポーネントの宣言
const Login: React.FC = () => {
return(....)
}
Loginという名前の関数コンポーネントを作るという宣言をしています。React.FCは「これは React の関数コンポーネントですよ」という型です。
ログイン処理
const navigate = useNavigate();
// Supabase の認証関数を呼び出し、成功時だけホーム画面へ移動する。
// ログイン機能の実装。失敗したら、処理を中断してアラートを表示する。
const handleLogin = async (email: string, password: string) => {
const loginResult = await loginUser(email, password);
if (loginResult.error) {
alert(`ログインに失敗しました: ${loginResult.error.message}`);
return;
} else {
console.log('ログイン成功:', loginResult.data);
navigate('/');
}
};
// react-hook-form の handleSubmit が、バリデーション成功時だけコールバックを実行する。
// ログインが成功したら、"/"に遷移する。
const onSubmit = handleSubmit((data) => {
handleLogin(data.email!, data.password!);
});
このコードはログインフォームを送信したときに認証して、成功したらトップページへ移動する処理です。
画面の移行に使う関数を取得
const navigate = useNavigate();
ログイン処理を任せる関数の作成
const handleLogin = async (email: string, password: string) => {
const loginResult = await loginUser(email, password);
if (loginResult.error) {
alert(`ログインに失敗しました: ${loginResult.error.message}`);
return;
} else {
console.log('ログイン成功:', loginResult.data);
navigate('/');
}
};
handleLoginは引数にemailとpasswordを受け取り、ログインの処理(loginUser)を実行します。awaitを使うことでログインの処理(loginUser)が終わるのを待ちます。
ログインの処理が実行されたら、loginResultに結果が返ってきます。もし失敗した場合は、「ログインに失敗しました」のアラートを出します。
フォーム送信時に動く処理
const onSubmit = handleSubmit((data) => {
handleLogin(data.email!, data.password!);
});
画面の送信ボタンが押された時に動く処理です。formで送信された値をdataで受け取り、handleLoginに渡し、実行する。
src/hooks/AuthContext.tsx
セッション取得、認証状態監視、useAuth を提供してます。
型の宣言
// 認証状態としてアプリ全体で参照したい値を定義
type AuthState = {
// 現在のセッション情報(未ログイン時は null)
session: Session | null;
// 現在ログイン中のユーザー情報(未ログイン時は null)
user: User | null;
// 初回セッション取得中かどうか
loading: boolean;
};
3つの型を宣言しています。現在のセッション状態(ログインしてるかどうか)を判断するsessstion、現在ログインしてるユーザー情報user、ロード画面などに使うloadingの3つです。
認証情報を共有する準備
const AuthContext = createContext<AuthState | null>(null);
認証情報をアプリ全体で共有するための変数を作っています。
createContext(...)が Context を作る関数です。Contextは親コンポーネントから子や孫コンポーネントまで、props を毎回渡さなくても値を共有できる仕組みです。これによって認証情報がアプリ全体で共有できます。 型の宣言でAuthStateornullが入ることを明示し、初期値にnullを入れます。
AuthProviderコンポーネント
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
このコンポーネントは、外側から受け取った children を包み込みます。使い方は以下です。
src/main.tsxに記載してます。
<AuthProvider>
<App />
</AuthProvider>
<App />はアプリ全体が入ってます。これを包むことで、認証情報をcontext経由で配ります。
useEffect(() => { ... }, [])
コンポーネントが最初、表示されたときに、1回だけ時移行します。
セッションの内容をまずは取得します。
supabase.auth.getSession().then(({ data, error }) => {
if (!error) setSession(data.session ?? null);
setLoading(false);
});
例えばユーザーがすでにログイン済みであれば、data.sessionに値が入り、未ログインなら data.session は null です。
エラーがなければ、session を state に保存しています。
次にこれは 今後の認証状態の変化を監視する登録処理 です。
const { data } = supabase.auth.onAuthStateChange((_event, nextSession) => {
setSession(nextSession);
setLoading(false);
});
onAuthStateChangeは認証状態の変化したら動きます。その際にnextSessionに最新の情報が入り、setSession(nextSession);でsesstionが最新の状態になります。
AuthProviderは<App />を囲ってるので、アプリが動いてる時は常に監視している状態です。
_eventは受け取るけど使わない値ですSupabase は通常、「何のイベントが起きたか」「新しい session は何か」の2つを渡してきます。「何のイベントが起きたか」については、ここでは使わないでアンダースコアが付けて記載してます。
最後に監視の解除を行います。
// コンポーネント破棄時に購読解除してリークを防ぐ
return () => data.subscription.unsubscribe();
普段の画面遷移では監視は消えないが、アプリ全体が壊れるときや、親から外されたときに消えるように処理を追加してます。
AuthContext の中身を安全に取り出すための専用フック
export const useAuth = () => {
// Context から認証状態を取得
const ctx = useContext(AuthContext);
// Provider 外で使われた場合は明示的にエラーにする
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
};
const ctx = useContext(AuthContext);でAuthContextの中身を取得し、エラーがあったら、if (!ctx) throw new Error('useAuth must be used within AuthProvider');でスローします。
おわりに
ログインページを作ってみたのですが色々と配慮しないといけない部分があり、かなり時間がかかってしまいました。
こうしてアウトプットすると流れがわかって、よかったです!