【Next.js】react-hook-formでFormProviderとuseFormContextを使い確認画面付きのフォームを作る
みなさんはReactでフォームを作成する場合は何を使用していますか?僕はreact-hook-formを使用しています。
やはり使用者の人数が多い分、ネットに多くの情報があるのもいいですね!
今回はそんなreact-hook-form使用した確認画面付きのフォームの作り方を紹介しようと思います!
目次
この記事を読むにあたって
まずこの記事を読むにあたって以下の知識がある方を対象としています。
- react-hook-formの基本的な使い方が分かる
- Next.jsを環境構築して少し触ったことがある(今回はそこまで重要ではありません)
問題無い!って方はこのまま読み進めてください。
まずはサンプルを用意したのでご確認ください!
※サンプルは送信ボタンを押してもどこにも情報は送信されませんのでご安心ください。
※以降はサンプルの内容をもとに進めて行きます。
1, 入力画面と確認画面のコンポーネントをFormProviderでラップする
// next.jsの機能
import { useRouter } from "next/router";
// components : pages
import Input from "components/pages/contact/Input"; //入力画面
import Confirm from "components/pages/contact/Confirm"; // 確認画面
//react-hook-form
import { useForm, FormProvider } from "react-hook-form";
const Contact = () => {
// パラメータを取得
const router = useRouter();
const isConfirm = router.query.confirm;
// useFormの設定&使用したい機能を呼び出す
const methods = useForm({
mode: "onChange",
criteriaMode: "all"
});
return (
<div className="wrapper">
<FormProvider {...methods}>
{!isConfirm ? (
<>
<Input />
</>
) : (
<>
<Confirm />
</>
)}
</FormProvider>
</div>
);
};
export default Contact;
FormProviderについて
FormProvider
とはuseForm
の機能をまとめて渡して、ラップされているコンポーネント内でuseFormContext
からその機能を使うことができます。
ここでラップされているコンポーネントは以下の通りです
- Inputコンポーネント(入力画面)
- Confirmコンポーネント(確認画面)
このコンポーネントの中身については後に解説します。
コンポーネントを切り替えるための値を取得
// パラメータを取得
const router = useRouter();
const isConfirm = router.query.confirm;
入力画面と確認画面のコンポーネントを切り替える値を設定しています。
コンポーネントを切り替える方法はいくつかありますが、ここではNext.jsのuseRouter
というhooksを使いクエリパラメータのconfirmの値の有無で切り替えます。
2, 入力画面を作成する
入力画面は1で登場したInputコンポーネント
のことです。
// next.jsの機能
import { useRouter } from "next/router";
//react-hook-form
import { useFormContext, SubmitHandler } from "react-hook-form"; // SubmitHandlerは、submitイベントに関する関数の型宣言に使う
import { ErrorMessage } from "@hookform/error-message"; //エラーメッセージコンポーネント
import type { ContactType } from "types/contact";
const Input = () => {
const router = useRouter();
const {
register, //inputなどに入力された値を参照するために使う
handleSubmit,
formState: { errors, isValid }
} = useFormContext();
const onSubmit: SubmitHandler<ContactType> = async (data) => {
console.log(data);
//ここでバリデーション用APIを叩くなど処理をする想定
router.push(`/?confirm=1`);
};
return (
<>
<h1>入力画面</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-unit">
<p className="form-unit-title">お名前</p>
<input
type="text"
className="input-text"
placeholder="山田太郎"
{...register("name", {
required: "お名前は必須項目です。"
})}
/>
<ErrorMessage
errors={errors}
name="name"
render={({ message }) =>
message ? <p className="form-validateMessage">{message}</p> : null
}
/>
</div>
<div className="form-unit">
<p className="form-unit-title">お問い合わせ内容</p>
<textarea
className="input-textarea"
placeholder="お問い合わせ内容を入力"
{...register("content", {
required: "お問い合わせ内容は必須項目です。"
})}
/>
<ErrorMessage
errors={errors}
name="content"
render={({ message }) =>
message ? <p className="form-validateMessage">{message}</p> : null
}
/>
</div>
<div className="form-actionArea">
{!isValid && (
<>
<p className="form-validateMessage">
まだ全ての必須項目の入力が完了していません。
</p>
</>
)}
<div className="form-buttonWrapper">
<button type="submit" className="form-submitButton">
入力内容を確認する
</button>
</div>
</div>
</form>
</>
);
};
export default Input;
useFormContextを使い必要な機能を呼び出す
入力画面のコンポーネントではuseFormContext
からFormProvider
に渡された機能を呼び出すことができます。
つまりpropsで渡さなくてもuseForm
の機能が使えるというわけです!素晴らしい!
あとはreact-hook-formを使える方であれば問題なくフォームを構築できるかと思います。(ほぼuseFormがuseFotmContextに置き換わっただけです)
submit時にクエリパラメータconfirmを付与
const onSubmit: SubmitHandler<ContactType> = async (data) => {
//ここでバリデーション用APIを叩くなど処理をする想定
router.push(`/?confirm=1`);
};
submit時にuseRouter
のpush
を使い?confirm=1
というクエリパラメータを付与された入力画面のURLにページ遷移します。
今回のサンプルではトップページ/
が入力画面なので/?confirm=1
となります。
すみません少しわかりにくいですよね.../contactとかの方がわかりやすかったですね。
※実際の実装ではここでバリデーション用のAPIなどを叩いて入力内容のチェックをやる想定です。そこで問題がなければuseRouter
のpush
を使います。
3, 確認画面を作成する
確認画面は1で登場したConfirmコンポーネント
のことです。
// next.jsの機能
import { useRouter } from "next/router";
import Link from "next/link";
//react-hook-form
import { useFormContext, SubmitHandler } from "react-hook-form"; // SubmitHandlerは、submitイベントに関する関数の型宣言に使う
import type { ContactType } from "types/contact";
const Confirm = () => {
const router = useRouter();
const {
handleSubmit,
getValues,
formState: { isValid } //form内の入力の有無や送信の状態などを取得できる isValid以外にも色々ある
} = useFormContext<ContactType>();
const values = getValues(); // 入力データを取得
//直アクセスの場合はリダイレクト
//※必須項目の無いフォームは無いと思うのでisValidで判定
if (!isValid) {
router.push(`/`);
}
const onSubmit: SubmitHandler<ContactType> = async (data) => {
//ここでメール送信などのAPIを叩くなど処理をする想定
router.push("/complete");
};
return (
<>
<h1>入力内容確認画面</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-unit">
<p className="form-unit-title">お名前</p>
<p>{values.name}</p>
</div>
<div className="form-unit">
<p className="form-unit-title">お問い合わせ内容</p>
<p>{values.content}</p>
</div>
<div className="form-actionArea">
<div className="form-buttonWrapper">
<button type="submit" className="form-submitButton">
送信する
</button>
<Link href="/">
<a className="form-backButton">入力内容を修正する</a>
</Link>
</div>
</div>
<p>※ただのサンプルなのでどこにも送信されません。</p>
</form>
</>
);
};
export default Confirm;
getValuesで入力データを取得する
useFormContext
からgetValues
を呼び出し、以下のように使用します。
const values = getValues(); // 入力データを取得
こうすることで、values.registerに設定したneme
で入力画面で入力したそれぞれのvalueを取得できます。
あとは以下のように記述することで入力内容を任意の場所に表示できます。
//例
<p>{values.name}</p> //入力画面で入力したお名前を表示
submit時に完了画面へ遷移
const onSubmit: SubmitHandler<ContactType> = async (data) => {
//ここでメール送信などのAPIを叩くなど処理をする想定
router.push("/complete");
};
入力画面と同様にsubmit時にuseRouter
のpush
を使います。
今回は完了画面のURLを/complete
としているので、そこにページ遷移させてあげます。
4, 完了画面について
const Complete = () => {
//直アクセスの場合は何かしらのフラグ管理をして、リダイレクト処理をする想定
return (
<div className="wrapper">
<h1>お問い合わせありがとうございました。</h1>
<p>
<a href="/">トップに戻る</a>
</p>
</div>
);
};
export default Complete;
完了画面は適当に作って問題ないかと思いますが、実際の実装で必要があれば送信完了フラグなどを立ててあげるといいかもしれません。
これで終わりです
これで解説は終わりとなります。
確認画面の表示までを簡単にまとめると
Input
コンポーネントとConfirm
コンポーネントをFormProvider
でラップするInput
コンポーネント内でuseFormContext
を使い入力画面を実装するConfirm
コンポーネント内でgetValues
を使い入力内容を表示する
このような感じです。
最後に
今回紹介した内容は超簡易版なので実際の実装ではAPIを叩いて確認ページに遷移する判定をしたり、確認画面や完了画面に直アクセスされときのリダイレクト処理だったり細かな調整が必要かと思います。
その他にも今回のサンプルはざっくりとInput
コンポーネントとConfirm
コンポーネントの2つしかコンポーネントはありませんし、cssもグローバルに適当に定義しているだけなので、このあたりは皆さんの得意な方法で好きに実装すると良いかと思います。