はじめに
この記事では、React(Next.js)で最近よく聞くTSと相性が良いZod
とReactでよく使われているReact Hook Form
を使ってフォームバリデーションの実装をしてみて、とても簡単で便利だなと感じたので紹介してみます。
今回やること
Next.jsでZod
とReact Hook Form
を組み合わせて使用してログイン機能のフォームバリデーションを実装してみます。
前提(準備)
今回下記2つを用いてフォームバリデーションを実装します。
自分の環境はフロントにReact(Next.js)とサーバーサイドにAPIサーバとしてPHP(Laravel)を使っています。
-
Zod
- 型安全なスキーマ定義とバリデーションを提供するライブラリ
-
React Hook Form
- React のフォーム状態管理を簡単にするためのカスタムフックを使用できるライブラリ
実際にやってみる
概要
以下のコードは、Next.js アプリケーションでログイン機能を実装するための一例です。
ログインフォームにメールアドレスとパスワードを入力し、送信するとサーバーでバリデーションが行われます。
バリデーションエラーがある場合、エラーメッセージが表示されます。
Zod スキーマ定義
- このスキーマでは、email は文字列で、最低 1 文字以上でメールアドレスの形式であること、password は文字列で、最低 8 文字以上であることが必要です。
const schema = z.object({
email: z
.string()
.min(1, { message: '入力必須です' })
.email({ message: 'メールアドレスの形式ではありません' }),
password: z.string().min(8, { message: '8文字以上入力する必要があります' }),
});
useForm の設定
次に、React Hook Form の useForm を使ってフォームの状態管理を設定します。
resolver
オプションに zodResolver(schema)
を渡すことで、Zod のバリデーションスキーマが適用されます。
const {
register,
watch,
handleSubmit,
formState: { errors, isDirty },
} = useForm({
resolver: zodResolver(schema),
});
useForm()
からはいくつかの関数が返されますが、今回は必要なものに限定してます。
register
はフォーム要素に適用するための属性を提供し、watch
はフォームの現在の値を取得するために使用されます。
handleSubmit
はフォームの送信処理を行います。
また、formState
から errors
と isDirty
を取得しています。
errors
はバリデーションエラーの情報が含まれ、isDirty
はフォームの値が変更されたかどうかを示すboolean
値です。
ログイン処理(カスタムフック)
下記のuseLogin()
をカスタムフックとして追加して、その中にlogin関数を追加します。
まず CSRF 保護のために /sanctum/csrf-cookie
エンドポイントを呼び出します(今回の内容には関係ないため説明はなし)
次に、入力されたメールアドレスとパスワードを /login エンドポイントに POST してログイン処理を行います。ログインに成功した場合、ホームページに遷移します。
ここでは、入力された値を先程useForm()
から取得したwatch()
関数から取得できます。
const useLogin = () => {
const router = useRouter();
const [validationMessage, setValidationMessage] = useState('');
const login = () => {
// Submit時の入力値を取得
const inputData = watch();
axiosApi
// CSRF保護の初期化
.get('/sanctum/csrf-cookie')
.then((res) => {
// ログイン処理
axiosApi
.post('/login', inputData)
.then((response: AxiosResponse) => {
// ログイン成功
router.push('/home');
})
// 省略: エラーハンドリング
});
};
return {
validationMessage,
login,
register,
handleSubmit,
errors,
isDirty,
};
}
useFormから返される各関数の説明:
- register: フォームの入力要素に渡す関数。name属性、ref、onBlur、onChangeが設定されます。
- watch: 入力値をリアルタイムで取得するための関数。
- handleSubmit: Submit時の処理をラップする関数。
- errors: バリデーションエラーを取得するためのオブジェクト。
- isDirty: 入力値が変更されたかどうかを示すブール値。
このフックでは、ログイン処理が成功した場合、ユーザーはホームページにリダイレクトされます。バリデーションエラーが発生した場合は、エラーメッセージが表示されます。
Loginコンポーネント
Loginコンポーネントでは、useLoginフックを使用してログインフォームを実装します。
const Login: React.FC = () => {
const { login, validationMessage, register, handleSubmit, errors, isDirty } =
useLogin();
//...省略
return (
<>
{/* ここにフォームの実装が続く */}
</>
);
};
フォームの実装では、register
関数を各入力要素に渡すことで、フォームの状態を管理します。
また、errors
オブジェクトを使用してバリデーションエラーメッセージを表示します。
そして、handleSubmit
関数をフォームのonSubmitイベントに渡します。これにより、フォームがサブミットされたときに先程カスタムフックにおいたlogin
関数が実行されます。
※inputタグがコンポーネントになっているのは、radix-ui(shadcn)を使用してます。
//...省略
<form onSubmit={handleSubmit(login)}>
<Input
id='email'
type='email'
placeholder='test@mail.com'
{...register('email')} // name, ref, onChange, onBlurが設定される
/>
{errors.email && (
<p className='py-3 text-sm'>
{errors.email.message as string}
</p>
)}
</form>
//...省略
最後に、handleSubmit関数をフォームのonSubmitイベントに渡します。これにより、フォームがサブミットされたときにlogin関数が実行されます。
終わりに
最初はregster
あたりが小難しく感じたのですが、実際に使ってみると普通にフォームバリデーションを実装してたときよりも、コードの記述力がとても減りました!
そしてなんといってもシンプルだしstate管理などを書かなくてよくなったのが楽ですね。
Unontrolled Components
のパターン(フォームやインプットなどのコンポーネントが、コンポーネント内部で値を管理して外部に依存しない)を採用しており、レンダリング回数を減らすことが出来るのも◯です。
zodも同様シンプルで実装しやすく型安全が確保できるので、よき組み合わせでした!
参考
- zod
- React Form Hook