👌

Next.js:フォーム間の値の受け渡しについて

2024/05/29に公開

はじめに

next.jsを用いた登録機能における入力画面から確認画面へのフォームの値の受け渡しについての実装方法をまとめてみました。

  • 事前準備
    まず、react-hook-formをインストールします。
npm install react-hook-form
 or
yarn add react-hook-form

今回の実装にあたり、useFormContext,FormProviderを使用します。

共通フォームの作成

親コンポーネントである共通フォーム(layout.tsx)を作成します。
入力画面(子コンポーネント)からuseFormContextを通して、共通フォーム(親コンポーネント)で値を受け取り、確認画面(子コンポーネント)に値を渡します。

app\register\layout.tsx

'use client'
import { FormProvider, useForm } from 'react-hook-form'

type FormValues = {
  name: string
  email: string
}

export default function Layout({ children }: { children: React.ReactNode }) {
  const methods = useForm<FormValues>({
    mode: 'onChange',
  });
  return <FormProvider {...methods}>{children}</FormProvider>;
}

useFormContextとは

FormProviderによって提供されるコンテキストからフォームのメソッドと状態を受け取るためのフックです。useFormContextを利用することで、異なるコンポーネント間でフォームの状態(入力データ、バリデーションエラーなど)や関数(handleSubmit, registerなど)を共有できます。この方法で、register、handleSubmit、errorsを親コンポーネント(共通フォーム)から子コンポーネントへ渡すことができます。

FormProviderとは

FormProviderは、useFormで生成されたmethods(フォームの状態とメソッドを含むオブジェクト)を子コンポーネントに提供するためのコンポーネントです。これにより、このコンポーネントの子要素はどこでもuseFormContextを使ってフォームの状態やメソッドにアクセスできるようになります。
{...methods}はJavaScriptのスプレッド構文を使っており、methodsオブジェクトの全プロパティをFormProviderに渡しています。

入力画面の作成

入力画面では、名前とメールアドレスの入力フォームを設けています。ユーザーがこれらの値を入力した後、handleSubmitを通じて確認画面への遷移が行われます。

app\register\page.tsx

"use client";

import { useFormContext } from "react-hook-form";
import { useRouter } from "next/navigation";
import React from "react";

const Register = () => {
  const router = useRouter();
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useFormContext();

  const onSubmit = (data) => {
    router.push("/register/confirm");
  };

  const onError = (errors) => {
    console.error("Validation errors:", errors);
  };

  return (
    <main className="">
      <div className="relative flex justify-center items-center min-h-screen bg-[register-pattern] text-gray-700">
        <div className="w-full max-w-md px-4 py-8 rounded">
          <h1 className="text-center text-gray-700 font-bold mb-6">入力画面</h1>
          <form onSubmit={handleSubmit(onSubmit, onError)}>
            <div className="mb-4">
              <label
                htmlFor="name"
                className="block text-gray-700 text-sm font-bold mb-2 text-xl"
              >
                お名前
              </label>
              <input
                type="text"
                id="name"
                {...register("name", {
                  required: "お名前は必須項目です",
                })}
                className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                placeholder="お名前を入力してください"
              />
              {errors.name && (
                <p className="text-red-500 text-xs italic">
                  {errors.name.message}
                </p>
              )}
            </div>
            <div className="mb-4">
              <label
                htmlFor="email"
                className="block text-gray-700 text-sm font-bold mb-2 text-xl"
              >
                メールアドレス
              </label>
              <input
                type="email"
                id="email"
                {...register("email", {
                  required: "メールアドレスは必須項目です",
                  pattern: {
                    value: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
                    message: "メールアドレスの形式が不正です",
                  },
                })}
                className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                placeholder="example@gmail.com"
              />
              {errors.email && (
                <p className="text-red-500 text-xs italic">
                  {errors.email.message}
                </p>
              )}
            </div>
            <div className="flex items-center justify-center space-x-4">
              <button
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-10 rounded focus:outline-none focus:shadow-outline w-56"
                type="submit"
              >
                確認画面へ
              </button>
            </div>
          </form>
        </div>
      </div>
    </main>
  );
};

export default Register;


  • useFormContextの宣言
const { register, handleSubmit ,formState: { errors }} = useFormContext();

useFormContextの詳しい使い方については公式ドキュメントを参考にしてください。

<公式ドキュメント>
https://react-hook-form.com/docs/useform

・入力画面

確認画面の作成

入力画面のフォームに入力した値を共通フォームを通して、確認画面で受け取ります。
app\register\confirm\page.tsx


import React from "react"; 
import { useRouter } from "next/navigation";
import { useFormContext } from "react-hook-form";

const Confirm = () => {
  const router = useRouter();
  const { getValues } = useFormContext();
  const values = getValues();

  return (
    <main>
      <div className="flex justify-center items-center min-h-screen bg-[register-pattern] text-gray-700">
        <div className="w-full max-w-md px-4 py-8 rounded">
          <h1 className="text-center text-gray-700 font-bold mb-6">
            確認画面
          </h1>
          <div className="mb-4">
            <h2 className="text-gray-700 text-sm font-bold mb-1">
              お名前
            </h2>
            <p className="text-gray-700 pl-4">
              ・{values.name}
            </p>
          </div>
          <div className="mb-4">
            <h2 className="text-gray-700 text-sm font-bold mb-1">
              メールアドレス
            </h2>
            <p className="text-gray-700 pl-4">
              ・{values.email}
            </p>
          </div>
        </div>
      </div>
    </main>
  );
};

export default Confirm;

・確認画面


階層図

next-aplication/
└── src/
    └── app/
        └── register/
            ├── page.tsx            
            ├── confirm/
            │   └── page.tsx        
            └── layout.tsx   

まとめ

いかがでしたでしょうか。この記事では、react-hook-formのuseFormContextとFormProviderを用いたフォーム処理の実装方法についてまとめました。また、Reactのフックについてこれからもさらに学びを深め、開発スキルを向上させるために努力していきたいと思います。フックの概念は初めは難しく感じましたが、実際に使ってみるとその便利さが分かり、もっと知りたいという気持ちになりました。

Discussion