はじめに
react-hook-form
と Zod
を組み合わせたフォーム実装を行った際、登録時と更新時でバリデーションが必要な項目が異なるケースがありました。
この記事では、そこでハマったポイントと解決策をまとめます。
問題の箇所
「新規登録画面」と「編集画面」で、共通のフォームを使っていました。
ただし、登録時はユーザーID(user_id
)の入力が必要ですが、編集時はユーザーIDを表示するだけで、編集できないようにしたいと考えていました。
最初は、新規登録用に作成した以下のスキーマを編集画面でも使い回そうとしました。
export const userFormSchema = z.object({
user_id: z
.string()
.nonempty('IDは必須です')
.min(3, 'IDは3文字以上で入力してください')
.regex(/^[a-zA-Z]+$/, 'IDは英単語のみ使用できます'),
name: z
.string()
.nonempty('名前は必須です')
.max(50, '名前は50文字以内で入力してください'),
// ...他のフィールド
});
export type UserFormSchemaType = z.infer<typeof userFormSchema>;
ところが、編集画面では user_id フィールドをフォームに含めておらず、表示専用のため、react-hook-form
が handleSubmit()
実行時に user_id
のバリデーションでエラーとなり、フォームが送信できませんでした。
問題内容
- バリデーションエラーでフォームが送信できない
-
handleSubmit()
のコールバックが呼ばれない
解決方法
Zodのomit
メソッドを活用する
調査の結果、Zodには便利なomit
メソッドがあることが分かりました。
これを使えば、既存のスキーマから特定のフィールドだけを除外したスキーマを簡単に作ることができます。
// 登録用スキーマ(user_idを含む)
export const userFormSchema = z.object({
user_id: z
.string()
.nonempty('IDは必須です')
.min(3, 'IDは3文字以上で入力してください')
.regex(/^[a-zA-Z]+$/, 'IDは英単語のみ使用できます'),
name: z
.string()
.nonempty('名前は必須です')
.max(50, '名前は50文字以内で入力してください'),
// ...他のフィールド
});
// 🔵編集用スキーマ(user_idを除外)
export const userEditFormSchema = userFormSchema.omit({ user_id: true });
export type UserFormSchemaType = z.infer<typeof userFormSchema>;
export type UserEditFormSchemaType = z.infer<typeof userEditFormSchema>;
編集コンポーネントでは、userEditFormSchema
とUserEditFormSchemaType
を使用します。
const {
register,
handleSubmit,
control,
setValue,
formState: { errors },
} = useForm<UserEditFormSchemaType>({
resolver: zodResolver(userEditFormSchema),
defaultValues: {
// user_idを削除
name: '',
description: '',
// ...他のデフォルト値
},
mode: 'onChange',
});
// ...省略...
// 更新処理
const onUpdateUser = async (data: UserEditFormSchemaType) => {
// ここでuser_idを追加
const formData = {
...data,
user_id: user?.user_id || '',
// ...他のデータ加工
};
// DBへの更新処理
const result = await updateUser(formData);
// ...省略...
};
このように omit
を使えば、共通したバリデーションルールを再定義する必要がなくなる上、バリデーションのルールを変更する時も1箇所で対応できます。
おわりに
フォームの一部のフィールドが異なるために、バリデーションスキーマを別で定義することは冗長のため避けたかったです。
そこでZod
のomit
を活用すれば、スキーマを再利用できてスッキリした構成になるのでおすすめです。
他にもおすすめの対応方法があれば、是非教えてください!
参考サイト