はじめに
いよいよ終盤です。
今回はフォームを追加できるようなフォームを作成していきます。
例えば追加ってボタンを押したらフォームが追加されるみたいな。
まずは名前とメールアドレス入力できたらいいでしょう。
理想はこんな感じ。
https://dev.classmethod.jp/articles/react-hook-form-use-field-array/
それではltgです
続き
ということでgithubです
せっかくなのでページ変えましょう。
pagesディレクトリの下にaddableForm.tsxみたいにして。
まずはスキーマ宣言して適当に体裁整えていきます。
nameとemailを配列で持つようにするのでこんな感じ。
const addableFormSchema = z.object({ profile: z.array(z.object({ name: z.string().min(1,"必須"), email: z.string().min(1,"必須").email("形式が不正") })) }) type addableFormSchema = z.infer<typeof addableFormSchema>
でuseFormを使います。
export default function AddableForm() { const { control, handleSubmit, reset } = useForm<addableFormSchema>({ mode: "onChange", resolver: zodResolver(addableFormSchema), }); const result = useFieldArray({ control: control, name: "profile", }); return <>{console.log(result)}</>;
こんな感じ。新しくuseFieldArrayを使います。
controlは自分もうまく言語化できませんが、
設定することで親元のformへ接続することが可能になるもので
nameはスキーマで宣言した配列の名前を設定し、これは必須です。
では http://localhost:3000/addableForm にとんで中身を見てみましょう。
何やら色々はいっているobjectみたいですね。
とりあえず直近の実務で使いそうなものはappend、fields、removeでしょうか。
appendは末尾にフォームを追加で
fieldsはそのFormに設定する配列そのもののデータで
removeはそのフォームを削除するみたいですね。
ということでそれらを取り出してまずはフォームと追加ボタンと送信ボタンを作ってみましょう。
できたのがこちら。
コードがこちら。
export default function AddableForm() { const { register, control, handleSubmit } = useForm<addableFormSchemaType>({ mode: "onChange", resolver: zodResolver(addableFormSchema), defaultValues: { profile: [{ name: "", email: "" }] }, }); const { fields, append, remove } = useFieldArray({ control: control, name: "profile", }); const onSubmit: SubmitHandler<addableFormSchemaType> = ( data: addableFormSchemaType, e?: BaseSyntheticEvent ) => console.log(data); const onError: SubmitErrorHandler<addableFormSchemaType> = ( errors: FieldErrors<addableFormSchemaType>, e?: BaseSyntheticEvent ) => console.log(errors); return ( <div className="flex items-center justify-center h-screen"> <form onSubmit={handleSubmit(onSubmit, onError)} className="flex flex-col gap-1" > {fields.map((field, index) => ( <div key={index} className="flex items-center gap-2 last:mb-3"> <input {...register(`profile.${index}.name`)} placeholder="名前" className="border p-1" /> <input {...register(`profile.${index}.email`)} placeholder="email" className="border p-1" /> <button type="button" onClick={() => append({ name: "", email: "" })} className="border hover:bg-slate-300 transition-all p-1" > 追加 </button> <button type="button" onClick={() => remove(index)} className="border hover:bg-slate-300 p-1" > 削除 </button> </div> ))} <div className="flex flex-col w-full gap-2"> <button type="submit" className="border hover:bg-blue-400 transition-all" > 送信 </button> </div> </form> </div> ); }
試しに3つに増やして、何も入力せずに送信して、errorsを見てみると
と出てきていました。
一方で全部正常に入力して送信し、中身を見ると
OKそうですね。
というわけで、またエラーをUIに表示するために修正します。
このエラー表示にデザインにてこづりました。grid使ってます。
flexでやるとどうにも片方しかエラーない時高さズレるので。
<div className="flex items-center justify-center h-screen"> <form onSubmit={handleSubmit(onSubmit, onError)} className="flex flex-col gap-1" > {fields.map((field, index) => ( <div key={index} className="last:mb-3"> <div className="grid grid-cols-12 gap-2"> <input {...register(`profile.${index}.name`)} placeholder="名前" className="border p-1 col-span-5" /> <input {...register(`profile.${index}.email`)} placeholder="email" className="border p-1 col-span-5" /> <button type="button" onClick={() => append({ name: "", email: "" })} className="border hover:bg-slate-300 transition-all p-1 col-span-1" > 追加 </button> <button type="button" onClick={() => remove(index)} className="border hover:bg-slate-300 transition-all p-1 col-span-1" > 削除 </button> </div> <div className="grid grid-cols-12 gap-2"> {errors.profile?.[index]?.name && ( <span className="text-red-500 text-sm col-span-5"> {errors.profile[index].name.message} </span> )} {errors.profile?.[index]?.email && ( <span className="text-red-500 text-sm col-span-5"> {errors.profile[index].email.message} </span> )} </div> </div> ))} <div className="flex flex-col w-full gap-2"> <button type="submit" className="border hover:bg-blue-400 transition-all" > 送信 </button> </div> </form> </div>
最後に今何件データがあるのかというのとエラーがあったら送信ボタンが押せないようにしましょう。
前者は{fields.length}でいけそうですね。
後者は formStateのisValidを新たに取り出す必要がありそうです。
ということで修正。
<div className="flex items-center justify-center h-screen"> <form onSubmit={handleSubmit(onSubmit, onError)} className="flex flex-col gap-1" > {fields.map((field, index) => ( <div key={index} className="last:mb-3"> <div className="grid grid-cols-12 gap-2"> <input {...register(`profile.${index}.name`)} placeholder="名前" className="border p-1 col-span-5" /> <input {...register(`profile.${index}.email`)} placeholder="email" className="border p-1 col-span-5" /> <button type="button" onClick={() => append({ name: "", email: "" })} className="border hover:bg-slate-300 transition-all p-1 col-span-1" > 追加 </button> <button type="button" onClick={() => remove(index)} disabled={fields.length === 1} className={`border p-1 col-span-1 ${ fields.length === 1 ? "bg-slate-300 cursor-not-allowed" : "hover:bg-slate-300 transition-all" }`} > 削除 </button> </div> <div className="grid grid-cols-12 gap-2"> {errors.profile?.[index]?.name && ( <span className="text-red-500 text-sm col-span-5"> {errors.profile[index].name.message} </span> )} {errors.profile?.[index]?.email && ( <span className="text-red-500 text-sm col-span-5"> {errors.profile[index].email.message} </span> )} </div> </div> ))} <div className="flex flex-col w-full gap-2"> <button type="submit" className={`border ${ !isValid ? "bg-slate-400 cursor-not-allowed" : "hover:bg-blue-400 transition-all" }`} disabled={!isValid} > 送信 {`${fields.length}件`} </button> </div> </form> </div>
正しい時も 機能していそうですね
ということで動的なフォームができました。
※ただ今気づきましたが、このフォームにはUIにおいてバグというかよろしくない 挙動を示します。見つけたらうまい具合に修正して教えてください。ぱくります。
終わりに
ということで1日でなんとか動的なフォームを作り切ることができました。
最初はzodとは?動的なフォームとは?というところからでしたが
react-hook-formについてもzodについても多少なりとも理解が進んだのでよかったです。
多分明日実務でこんな感じのフォームを作るので今日中にしれてよかったです。
実務はこれにapi通信が絡んだり項目が多かったり、初期値に前もって入れていたりと
そういう情報が加わるので複雑ですが頑張りたいです。
何はともあれよかったです。
これにて一旦旅は終了です。お疲れ様でした。