お問い合わせフォームの確認画面をモーダルウィンドウで表示する。【ReactHookForm】
React Hook Formを使ったお問い合わせフォームと、入力内容の確認画面をモーダルウィンドウで表示する例を紹介します。
React Hook Formとモーダルウィンドウに関する情報は。インターネットにいくつか転がっていますが、確認画面をモーダルウィンドウにして表示する組み合わせが思ったより少ないので作成しました。
フレームワーク・ライブラリ
- Next.js 14.2.0
- React Hook Form 7.51.3
- Zod 3.22.4
- @hookform/resolvers 3.3.4
DEMO
導入方法
npm install react-hook-form @hookform/resolvers zod
ソースコード
page.tsx
FormProvider
:便利機能。
FormProvider
を使って<form>
タグ とフォーム関連の処理をラップすることで、propsを渡すことなくフォームの値をやり取りできます。
モーダルウインドウの表示制御にはuseState
を使って制御しています。
Zod
, useForm
の使い方を紹介している記事は、多いので割愛します。
'use client';
import { useForm, FormProvider } from "react-hook-form"
import { Constaraint } from './schema'
import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import Form from './form';
import Confirm from './confirm';
type formType = {
name: string;
email: string;
message: string;
}
export default function App() {
// フォームエラーの制御
const methods = useForm<formType>({
mode: "onBlur",
resolver: zodResolver(Constaraint)
})
// モダールウィンドウの制御
const [isOpen, setIsOpen] = useState(false);
return (
<>
<main className="min-h-screen bg-gray-200 w-full p-8">
<div className="max-w-xl">
<FormProvider {...methods}>
<Form onOk={() => setIsOpen(true)}/>
<Confirm open={isOpen} onCancel={() => setIsOpen(false)} onOk={() => (setIsOpen(false))}/>
</FormProvider>
</div>
</main>
</>
)
}
form.tsx
ココでは、useFormContext
を使ってフォームの値を取得しています。useFormContext
を使うことで、propsを渡すことなくコンポーネント間でフォームの値をやり取りできます。
handleSubmit
を使って、フォームの値を引数にしてonOk
関数を実行しています。ボタンが押下されると、page.tsx
にあるonOk={() => setIsOpen(true)}
が実行され、Confirm
コンポーネントが表示されます。
'use client';
import { useFormContext } from "react-hook-form";
type formType = {
name: string;
email: string;
message: string;
}
type Props = {
onOk: () => void;
}
export default function Form({ onOk }: Props) {
const methods = useFormContext<formType>();
const {
register,
handleSubmit,
formState: {errors}
} = methods;
return(
<>
<form onSubmit={ handleSubmit(onOk) } className="bg-white shadow-md rounded p-8">
<h3 className="block text-gray-700 text-lg font-bold mb-2">お問い合わせ</h3>
<div className="mb-4">
<label htmlFor="名前" className="block text-gray-700 text-sm font-bold mb-2">お名前</label>
<input {...register("name")} type="text" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-1 leading-tight focus:outline-none focus:shadow-outline"/>
<p className="text-red-500 text-xs italic mb-4">{ errors.name?.message }</p>
<label htmlFor="メール" className="block text-gray-700 text-sm font-bold mb-2">メールアドレス</label>
<input {...register("email")} type="email" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-1 leading-tight focus:outline-none focus:shadow-outline"/>
<p className="text-red-500 text-xs italic mb-4">{ errors.email?.message }</p>
<label htmlFor="お問い合わせ" className="block text-gray-700 text-sm font-bold mb-2">お問い合わせ内容</label>
<textarea {...register("message")} rows={8} className="resize-none shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-1 leading-tight focus:outline-none focus:shadow-outline"/>
<p className="text-red-500 text-xs italic mb-4">{ errors.message?.message }</p>
</div>
<div className="flex items-center justify-center">
<button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-8 rounded focus:outline-none focus:shadow-outline">
確認
</button>
</div>
</form>
</>
)
}
confirm.tsx
モーダルウィンドウによる確認画面を表示しています。
useFormContext
を使ってフォームの値を取得して、表示しています。
送信ボタンを押した場合、handleSubmit
を使って、フォームの値を引数にしてSendAction
関数を実行しています。
SendAction
関数内に、送信処理を記述します。
キャンセルボタン、画面外をクリックした場合、onCancel
を実行してモーダルウィンドウを閉じます。
'use client';
import { useFormContext } from "react-hook-form";
import {SendMessageAction } from "./action";
type formType = {
name: string;
email: string;
message: string;
}
type Props = {
open: boolean;
onOk: () => void;
onCancel: () => void;
}
export default function Confirm({ open, onOk, onCancel }:Props) {
const methods = useFormContext<formType>();
const {
getValues,
handleSubmit,
} = methods;
const SendAction = (data :formType) => {
//ここから送信処理を実装する。
otherAction(data);
onOK();
};
return open ?(
<>
<form onSubmit={handleSubmit(SendAction)} className="bg-white top-1/4 left-1/2 transform -translate-x-1/2 w-1/4 p-5 flex flex-col items-start absolute z-20">
<h2 className="text-xl font-bold mb-5">送信内容の確認</h2>
<label className="text-gray-700 text-sm font-bold mb-2">お名前</label>
<p className="text-lg mb-5">{ getValues('name') }</p>
<label className="text-gray-700 text-sm font-bold mb-2">メールアドレス</label>
<p className="text-lg mb-5 w-full overflow-hidden">{ getValues('email') }</p>
<label className="text-gray-700 text-sm font-bold mb-2">お問い合わせ内容</label>
<p className="text-lg mb-5 break-all max-h-60 overflow-y-auto">{ getValues('message') }</p>
<div className="flex mt-auto w-full">
<button type="submit" className="bg-slate-900 hover:bg-slate-700 text-white px-8 py-2 mx-auto">
送信
</button>
<button type="button" onClick={ onCancel } className="bg-slate-900 hover:bg-slate-700 text-white px-8 py-2 mx-auto">
キャンセル
</button>
</div>
</form>
{/*モーダルウィンドウ以外をクリックした場合の処理*/}
<div onClick={ onCancel } className="fixed bg-black bg-opacity-50 w-full h-full top-0 left-0 z-10"/>
</>
):(<></>);
};
schema.tsx
Zod
を使って、フォームのバリデーションルールを定義しています。
import { z } from 'zod';
export const Constaraint = z.object({
name: z.string()
.min(1, { message: '入力が必須の項目です' })
.max(20, { message: '20文字以内で入力してください' })
.regex(/^[^<>"'\\/]*$/, { message: '特殊文字は使用できません' }),
email: z.string()
.min(1, { message: '入力が必須の項目です' })
.max(255, { message: '255文字以内で入力してください' })
.email({ message: 'メールアドレスの形式で入力してください' }),
message: z.string()
.min(10, { message: '入力が必須の項目です' })
.max(1000, { message: '1000文字以内で入力してください' })
.regex(/^[^<>"'\\/]*$/, { message: '特殊文字は使用できません' }),
});
おわりに
React Hook Formを使ったお問い合わせフォームの作成と、確認画面をモーダルウインドウで表示する例を紹介しました。
お問い合わせフォームに、わざわざ確認画面を表示させるなど冗長な感じもしますが、ユーザーの操作ミスを防ぐなどの効果があります。
何かの参考になれば幸いです。
Discussion