Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Hook Form と Zod によるフォームバリデーション

Avatar for SAW SAW
November 07, 2025

React Hook Form と Zod によるフォームバリデーション

フロントエンド勉強会 in 大阪 の発表資料です。

Avatar for SAW

SAW

November 07, 2025
Tweet

More Decks by SAW

Other Decks in Programming

Transcript

  1. $(whoami) w ࢯ໊Ճ౻फҰ࿠ ࡀ  w ϋϯυϧωʔϜ4"8 w 9 چ5XJUUFS

    !B[VLJ@FBUFS w ؔ੢ͷ*5ΤϯδχΞίϛϡχςΟͷ೐΍͔͠ ࣗশ  w େࡕࡏॅɾѪ஌ग़਎ w ಘҙ෼໺8FCΞϓϦέʔγϣϯ։ൃ w -BSBWFM 7VF w ॴଐ༗ݶձࣾΞϦ΢ʔϓ 2 ඇৗ༻ίοΫͷதʹ খ͞ͳίοΫ͞Μ͸ೖ͍ͬͯͳ͍ ࠓ೔ͷ໎ݴ
  2. ϑΥʔϜόϦσʔγϣϯͷඞཁੑ w ϢʔβʔͷೖྗϛεΛϑΥʔϜͷૹ৴લʹݕग़ w ೖྗඞਢͷ߲໨͕ೖྗ͞Ε͍ͯͳ͍ w ϝʔϧΞυϨε΍༣ศ൪߸ͳͲͷೖྗܗ͕ࣜਖ਼͘͠ͳ͍ w ೖྗ͞Εͨจࣈ਺͕ࢦఆͷൣғ಺ʹऩ·͍ͬͯͳ͍ w

    Ͳͷ߲໨ʹΤϥʔ͕͋Δ͔Ϣʔβʔʹࢹ֮తʹೝࣝͤ͞Δ w ೖྗΤϥʔͷ͋Δ߲໨ʹରͯ͠Τϥʔ಺༰ͷϝοηʔδΛදࣔ w Ϣʔβʔ͸Τϥʔϝοηʔδͷ಺༰Λ֬ೝͯ͠ଈ࠲ʹೖྗΛमਖ਼Ͱ͖Δ 3
  3. ϑΥʔϜόϦσʔγϣϯͷ࣮ݱ w ϑΥʔϜͷ֤ϑΟʔϧυͷೖྗΛͦΕͧΕνΣοΫ w ֤ϑΟʔϧυʹରͯ͠όϦσʔγϣϯϧʔϧΛఆٛͯ͠ద༻ w ֤ϑΟʔϧυͷঢ়ଶ ݕূ݁ՌͳͲ ͷ؅ཧ͕ඞཁ w

    Τϥʔ͕͋ΔϑΟʔϧυʹରͯ͠ΤϥʔϝοηʔδΛద੾ʹදࣔ w ϑΟʔϧυͷݕূ݁Ռʹج͍ͮͯΤϥʔͷදࣔɾඇදࣔͷঢ়ଶͷ؅ཧ͕ඞཁ w ϑΥʔϜͷ؅ཧϥΠϒϥϦΛར༻͢Δ͜ͱͰ࣮૷Λ؆ૉԽ w ϑΥʔϜͷঢ়ଶ؅ཧ w όϦσʔγϣϯͱΤϥʔϝοηʔδͷද੍ࣔޚ 4
  4. ϑΥʔϜσʔλͷܕఆٛ w ϑΥʔϜͰѻ͏σʔλͷܕΛఆٛ w ఆٛͨ͠ܕΛuseForm()ͷܕҾ਺ʹࢦఆՄೳ w ϑΥʔϜϑΟʔϧυͷ஋ͷܕࢦఆ͕ՄೳʹͳΔ 8 import {

    useForm } from 'react-hook-form'; interface FormInput { name: string; email: string; age: number; } useForm<FormInput>();
  5. ϑΥʔϜͷૹ৴ॲཧ w VTF'PSNIPPL͔ΒhandleSubmit()Λऔಘ w formཁૉͷonSubmitʹhandleSubmit()Λࢦఆ w handleSubmit()ͷҾ਺ʹϑΥʔϜૹ৴࣌ʹ࣮ߦ͢Δؔ਺Λࢦఆ 10 const {

    register, handleSubmit } = useForm(); const onSubmit = (data: FormInput) => { // 送信時の処理 }; return ( <form onSubmit={handleSubmit(onSubmit)}> <!-- 略 --> </form> );
  6. ϑΥʔϜͷόϦσʔγϣϯ w register()ͷୈҾ਺ʹόϦσʔγϣϯϧʔϧΛࢦఆ w ඞਢ΍࠷খ஋΍࠷େ஋ͳͲΛࢦఆՄೳ w Τϥʔϝοηʔδͷઃఆ΋Մೳ 11 const {

    register } = useForm<FormInput>(); return ( <form> <input {...register('name', { required: { value: true, message: '必須項目です', } })} /> <!-- 略 --> </form> );
  7. Τϥʔϝοηʔδͷදࣔ w VTF'PSNIPPL͔ΒformStateΛऔಘͯ͠errorsΛऔΓग़͢ w errorsʹ͢΂ͯͷϑΥʔϜϑΟʔϧυͷΤϥʔΛแؚ w register()Ͱࢦఆͨ͠ϑΟʔϧυ໊ͱerrorsͷϓϩύςΟ໊͕ରԠ 12 const {

    /* 略 */ formState: { errors } } = useForm(); return ( <form> <input {...register('name', { /* 略 */ }) /> {errors.name && (<span>{errors.name.message}</span>)} </form> );
  8. ;PEͱ͸ w 5ZQF4DSJQU fi STUͳόϦσʔγϣϯϥΠϒϥϦ w ܕʹج͍ͮͯεΩʔϚΛఆٛ w จࣈྻͷΑ͏ͳ୯७ͳ΋ͷ͔Βෳࡶͳߏ଄·ͰॊೈʹఆٛՄೳ w

    ఆٛͨ͠εΩʔϚʹج͍ͮͯೖྗ஋Λݕূ w ಛఆͷϥΠϒϥϦ΍ϑϨʔϜϫʔΫʹґଘ͠ͳ͍ w 3FBDU)PPL'PSNҎ֎ͷϑΥʔϜϥΠϒϥϦʹ΋ར༻Մೳ w 7VFͳͲͷ3FBDUҎ֎ͷϑϨʔϜϫʔΫͰ΋ར༻Մೳ 14
  9. εΩʔϚͷఆٛ w ;PEͷϝιουΛར༻ͯ͠εΩʔϚͷܕΛఆٛ w string()΍number()ͳͲͰجຊܕΛදݱ w object()ͰΦϒδΣΫτΛදݱ w Ҿ਺ͷΦϒδΣΫτͰ͞Βʹ;PEͷεΩʔϚͷܕఆ͕ٛՄೳ w

    ೖΕࢠߏ଄ͷεΩʔϚͷఆٛ΋Մೳ w array()Ͱ഑ྻΛදݱ 16 import * as z from 'zod'; const UserSchema = z.object({ name: z.string(), email: z.email(), age: z.number(), });
  10. ೖྗ஋ͷݕূ w parse()Λར༻ͯ͠εΩʔϚͷఆ͔ٛΒೖྗ஋Λݕূ w όϦσʔγϣϯΤϥʔ͕͋Δ৔߹͸ZodError͕UISPX͞ΕΔ w safeParse()Λར༻ͯ͠ݕূ݁ՌͷΦϒδΣΫτΛऔಘ w safeParse()Ͱ͸ZodError͸UISPX͞Εͳ͍ w

    ݕূ݁ՌͷΦϒδΣΫτΛฦ٫ w ฦ٫ܕ͸ݕূ੒ޭɾࣦഊͷ݁ՌͷVOJPOܕ 17 try { UserSchema.parse({ name: 'foo', age: 31 }); } catch (error) { if (error instanceof z.ZodError) { // エラー処理 } }
  11. 3FBDU)PPL'PSNʹ;PEΛઃఆ w useForm()ͷΦϓγϣϯͷresolverʹzodResolver()Λࢦఆ w zodResolver()ͷҾ਺ʹ͸;PEͰఆٛͨ͠εΩʔϚΦϒδΣΫτΛࢦఆ w useForm()ͷܕҾ਺ʹ;PEͰੜ੒ͨ͠ܕΛࢦఆ w register()ͷόϦσʔγϣϯϧʔϧͷઃఆ͸ෆཁ w

    ;PEͷεΩʔϚఆٛʹج͍ͮͯࣗಈతʹόϦσʔγϣϯ͞ΕΔ 22 import { zodResolver } from '@hookform/resolvers/zod'; const { /* 略 */ } = useForm<UserType>({ resolver: zodResolver(UserSchema), });