10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactでログイン機能を実装したので流れを整理してみた

10
Posted at

はじめに

スケジュールを立てる能力を育てるアプリを開発しました。
ログインが面を実装したのだが、それの実装に四苦八苦したので、備忘録として書いておきます。

使用ファイル

ログインに使っているファイルは以下です。

  • 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は引数にemailpasswordを受け取り、ログインの処理(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.sessionnull です。
エラーがなければ、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');でスローします。

おわりに

ログインページを作ってみたのですが色々と配慮しないといけない部分があり、かなり時間がかかってしまいました。
こうしてアウトプットすると流れがわかって、よかったです!

参考

Git hubプロジェクト
アプリ画面

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?