エンジニアの備忘録

学んだ事をアウトプットしていきます

react-hook-formとFirebaseでログイン画面とログイン機能

バージョン

  • react:18.0
  • next.js:13.5.6
  • typescript:5.0

前準備

  1. VSコードから使用するディレクトリに移動
  2. コマンド:「npx create-next-app@latest」 で作成
  3. プロジェクトネームを作成し、import alias? のみNoで、他はYesにして進む
  4. 作成したプロジェクトに移動
  5. コマンド:「code.」 で立ち上げ
  6. コマンド:「npm run dev」 でローカル環境が起動

ソースファイルの前準備

  • app/layout の lang を ja に変更しておく
  • app/globals.css の @tailwind 以外は削除しておく

Firebaseのセットアップ(初期化)

  1. https://console.firebase.google.com/u/0/?hl=ja  にログイン

  2. プロジェクトの追加をクリック
  3. アナリティクスは無効で次へ
    アナリティクス:ユーザーがどれくらい訪れたか?等が分かる機能
  4. 続行
  5. をクリックして次へ
  6. ネーム入力、Hostingは一旦空で次へ
    Firebase Hosting はデプロイで使う機能
  7. 何も変更せず、コンソールに進むをクリック
  8. プロジェクトの設定をクリック

  9. 「npm install firebase」をコピーしてVSCodeに戻りコマンドを叩く
  10. 次に Firebase を初期化し、使用するプロダクトの SDK の利用を開始します。の下記ソースコードをコピーしておく
  11. src\app 直下に「firebase.ts」ファイルを作成
  12. コピーしたソースを張り付ける
    ※apiKey や authDomain は 見られたらいけないので、git管理するならenvファイルや .gitignore を使うこと
  13. firebase.ts ファイルに書き加えて、初期化完了
    import { initializeApp } from "firebase/app";
    import { getAuth } from "firebase/auth";
    import { getFirestore } from "firebase/firestore";

    const firebaseConfig = {
      apiKey: "AIzaSyALIAt79-kG5Z0CzpBLc_KSVexcHOK-HGU",
      authDomain: "chatapplication-with-cha-ac9a9.firebaseapp.com",
      projectId: "chatapplication-with-cha-ac9a9",
      storageBucket: "chatapplication-with-cha-ac9a9.appspot.com",
      messagingSenderId: "745628687411",
      appId: "1:745628687411:web:1b2274a27ea9f17c4dd746",
    };

    const app = initializeApp(firebaseConfig);
    export const db = getFirestore(app);
    export const auth = getAuth(app);

新規登録ページの作成

CSS記法はTaillwind

import React from "react";

const Register = () => {
  return (
    <>
      <div className="h-screen flex flex-col items-center justify-center">
        <form className="bg-white p-8 rounded-lg shadow-md w-96">
          <h1 className="mb-4 text-2xl text-gray-700 font-medium font-">
            新規登録
          </h1>
          <div className="mb-4">
            <label className="block text-sm font-medium">メールアドレス</label>
            <input
              type="text"
              className="mt-1 border-2 rounded-md w-full p-2"
            ></input>
          </div>
          <div className="mb-4">
            <label className="block text-sm font-medium">パスワード</label>
            <input
              type="password"
              className="mt-1 border-2 rouded-mt w-full p-2"
            ></input>
          </div>
          <div className="justify-end flex pb-3">
            <button
              type="submit"
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded-md"
            >
              新規登録
            </button>
          </div>
          <div>
            <span>既にアカウントをお持ちの方:</span>
            <button className="text-blue-500 hover:text-blue-800 font-bold">
              ログインページへ
            </button>
          </div>
        </form>
      </div>
    </>
  );
};

export default Register;

 

新規登録機能

打ち込んだメールアドレスのバリデーションチェック

"use client";

import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  email: string;
  password: string;
};

const Register = () => {
  const {
    // react側が用意しているプロパティ
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>();

  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    console.log(data);
  };

  return (
    <>
      <div className="h-screen flex flex-col items-center justify-center">
        <form
          onSubmit={handleSubmit(onSubmit)}
          className="bg-white p-8 rounded-lg shadow-md w-96"
        >
          <h1 className="mb-4 text-2xl text-gray-700 font-medium font-">
            新規登録
          </h1>
          <div className="mb-4">
            <label className="block text-sm font-medium">メールアドレス</label>
            <input
              // requiredの値がエラーとして表示される
              {...register("email", { required: "メールアドレスは必須です" })}
              type="text"
              className="mt-1 border-2 rounded-md w-full p-2"
            />
            {/* emailのバリデーションエラー */}
            {errors.email && (
              <span className="text-red-500 text-sm">
                {errors.email?.message}
              </span>
            )}
          </div>
  ~省略~

 

正規表現のバリデーションチェック

<div className="mb-4">
            <label className="block text-sm font-medium">メールアドレス</label>
            <input
              // requiredの値がエラーとして表示される
              {...register("email", {
                required: "メールアドレスは必須です",
                // 正規表現
                pattern: {
                  value:
                    /^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/,
                  message: "正しい形式で入力してください",
                },
              })}
              type="text"
              className="mt-1 border-2 rounded-md w-full p-2"
            />
            {/* emailのバリデーションエラー */}
            {errors.email && (
              <span className="text-red-500 text-sm">
                {errors.email?.message}
              </span>
            )}
          </div>
          <div className="mb-4">
            <label className="block text-sm font-medium">パスワード</label>
            <input
              {...register("password", {
                required: "パスワードは必須です",
                // 文字数制御
                minLength: {
                  value: 6,
                  message: "6文字以上で入力してください。",
                },
              })}
              type="password"
              className="mt-1 border-2 rouded-mt w-full p-2"
            />
          </div>
          {errors.password && (
            <span className="text-red-500 text-sm">
              {errors.password?.message}
            </span>
          )}

 

新規登録を押すとデータが取得される。このデータをfirebaseに登録して新規登録を行う。

Firebaseにユーザー登録

  1. ユーザー管理の為firebaseからをクリック

  2. メール&パスワードを有効にして保存
  3. ウェブサイトで Firebase Authentication を使ってみるの新しいユーザーを登録するを参考に書く

import { auth } from "@/app/firebase";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  email: string;
  password: string;
};

const Register = () => {
 
  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    await createUserWithEmailAndPassword(auth, data.email, data.password)
      .then((userCredential) => {
        const user = userCredential.user;
      })
      .catch((error) => {
        alert(error);
      });
  };

 

新規登録を押すとユーザーが登録される

useRouterを使いログインページに遷移

  • pushに飛ばしたいパスを指定
  • 今回だと新規登録を押したらログインページに飛ぶ
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("auth/login")

 

ログインページ作成

  • 新規登録ページをコピペして部分的に変更
    • ログインの場合は signInWithEmailAndPassword() を使用する
    • ログインしたらルートディレクトリに飛ばす
    • ログイン失敗のエラー表示
    • 新規登録へ の部分はnextのLinkコンポーネントを使用
"use client";

import { auth } from "@/app/firebase";
import { error } from "console";
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
} from "firebase/auth";
import { useRouter } from "next/navigation";
import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  email: string;
  password: string;
};

const Register = () => {
  const router = useRouter();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>();

  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    await signInWithEmailAndPassword(auth, data.email, data.password)
      .then((userCredential) => {
        const user = userCredential.user;
        router.push("/");
      })
      .catch((error) => {
        if (error.code === "auth/invalid-credential") {
          alert("存在しないユーザーです。");
        } else {
          alert(error.message);
        }
      });
  };

  return (
    <>
      <div className="h-screen flex flex-col items-center justify-center">
        <form
          onSubmit={handleSubmit(onSubmit)}
          className="bg-white p-8 rounded-lg shadow-md w-96"
        >
          <h1 className="mb-4 text-2xl text-gray-700 font-medium font-">
            ログイン
          </h1>
          <div className="mb-4">
            <label className="block text-sm font-medium">メールアドレス</label>
            <input
              // requiredの値がエラーとして表示される
              {...register("email", {
                required: "メールアドレスは必須です",
                // 正規表現
                pattern: {
                  value:
                    /^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/,
                  message: "正しい形式で入力してください",
                },
              })}
              type="text"
              className="mt-1 border-2 rounded-md w-full p-2"
            />
            {/* emailのバリデーションエラー */}
            {errors.email && (
              <span className="text-red-500 text-sm">
                {errors.email?.message}
              </span>
            )}
          </div>
          <div className="mb-4">
            <label className="block text-sm font-medium">パスワード</label>
            <input
              {...register("password", {
                required: "パスワードは必須です",
                // 文字数制御
                minLength: {
                  value: 6,
                  message: "6文字以上で入力してください。",
                },
              })}
              type="password"
              className="mt-1 border-2 rouded-mt w-full p-2"
            />
          </div>
          {errors.password && (
            <span className="text-red-500 text-sm">
              {errors.password?.message}
            </span>
          )}
          <div className="justify-end flex pb-3">
            <button
              type="submit"
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded-md"
            >
              ログイン
            </button>
          </div>
      <div>
            <span>アカウントをお持ちでない方:</span>
            <Link
              href={"/auth/register"}
              className="text-blue-500 hover:text-blue-800 font-bold"
            >
              新規登録へ
            </Link>
          </div>
        </form>
      </div>
    </>
  );
};

export default Register;

 

ログイン画面