LPサイトのお問い合わせフォームのバリデーションを作成する時に使ってみた組み合わせを紹介
PR
frontendのバリデーションを実装
以下を使用してバリデーションを実装
yupでは、バリデーションを以下のように実装できる。
import * as yup from "yup"; const schema = yup.object().shape({ name: yup.string().required("名前の入力は必須です").max(100, "名前は最大100文字までです"), email: yup.string().required("Emailの入力は必須です").email("メールアドレスの形式になっていません"), }); const request = async() => { const value = { name: "", email: "test@gmail.com" } try { await schema.validate(value); } catch(e) { console.log(err.errors); // ["名前の入力は必須です"] } }
ただのバリデーションだったらyupでも簡単に実装可能だが、
フォームに以下のようなエラー表示の実装をしようと思うとyupだけだとerrorをstateで管理しないと実装できない。
そこでフォームの値の管理とバリデーションを同時に行うためにformikを使用した。
formikはReact Hooksを使用して以下のように実装できる
import { useFormik } from "formik"; import * as yup from "yup"; import "react"; type State = { name: string; email: string; }; const initialState = (): State => { return { name: "", email: "", }; }; const schema = yup.object().shape({ name: yup.string().required("名前の入力は必須です").max(100, "名前は最大100文字までです"), email: yup.string().required("Emailの入力は必須です").email("メールアドレスの形式になっていません"), }); const Form = () => { const formik = useFormik<State>({ initialValues: initialState(), validationSchema: schema, onSubmit: (values) => { console.log(values) }, }); return ( <form onSubmit={formik.handleSubmit}> <input type="text" id="name" name="name" onChange={formik.handleChange} /> {formik.touched.name && formik.errors.name ? ( <div>{formik.errors.name}</div> ) : null} <input type="text" id="email" name="email" onChange={formik.handleChange} /> {formik.touched.email && formik.errors.email ? ( <div>{formik.errors.email}</div> ) : null} <button type="submit">送信</button> </form> ) }
上記でフォームの値管理とバリデーションが行えた。あとはcssを整えて以下のように実装
■ components/organisms/Contact/Form.tsx
import { useFormik } from "formik"; import { useRouter } from "next/router"; import { memo, useState, useCallback } from "react"; import * as yup from "yup"; type State = { name: string; email: string; body: string; }; const initialState = (): State => { return { name: "", email: "", body: "", }; }; type Request = { body: string; name: string; email: string; env: string; userID: string; device: string; category: string; }; const schema = yup.object().shape({ name: yup.string().required("名前の入力は必須です").max(100, "名前は最大100文字までです"), email: yup.string().required("Emailの入力は必須です").email("メールアドレスの形式になっていません"), body: yup.string().required("本文の入力は必須です").max(2000, "本文は最大2000文字までです"), }); const url = process.env.NEXT_PUBLIC_INQUIRY_API || ""; const Form = () => { const router = useRouter(); const [loading, setLoading] = useState(false); const formik = useFormik<State>({ initialValues: initialState(), validationSchema: schema, onSubmit: (values) => { postInquiry(values); }, }); const postInquiry = useCallback( async (values: State) => { const userAgent = navigator.userAgent.toLowerCase(); setLoading(true); const req: Request = { body: values.body, name: values.name, email: values.email, userID: "", env: "LPサイト", device: userAgent, category: "LPお問い合わせ", }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(req), }); setLoading(false); if (!response.ok) { alert("送信に失敗しました"); return; } router.push(`/thanks?name=${values.name}`); }, [router] ); return ( <form onSubmit={formik.handleSubmit}> <div className="flex z-10 flex-col justify-center items-center py-3 md:py-16 my-3"> <div className="mb-0 md:mb-6 text-3xl leading-snug text-center">お問い合わせ</div> <br /> <div className="mr-4"> <div className="flex items-center my-5"> <div className="mr-2 md:mr-5 w-10 text-right">名前</div> <div className="w-80 md:w-96"> <input className="py-2 px-3 w-full leading-tight rounded border shadow appearance-none" type="text" id="name" name="name" aria-label="name" onChange={formik.handleChange} /> {formik.touched.name && formik.errors.name ? ( <div className="text-sm text-red-600">{formik.errors.name}</div> ) : null} </div> </div> <div className="flex items-center my-5"> <div className="mr-2 md:mr-5 w-10 text-right">Email</div> <div className="w-80 md:w-96"> <input className="py-2 px-3 w-full leading-tight rounded border shadow appearance-none " type="text" id="email" name="email" aria-label="email" onChange={formik.handleChange} /> {formik.touched.email && formik.errors.email ? ( <div className="text-sm text-red-600">{formik.errors.email}</div> ) : null} </div> </div> <div className="flex my-5"> <div className="mt-1 mr-2 md:mr-5 w-10 text-right">本文</div> <div className="w-80 md:w-96"> <textarea className="py-2 px-3 w-full leading-tight rounded border shadow appearance-none " rows={8} id="body" name="body" aria-label="body" onChange={formik.handleChange} /> {formik.touched.body && formik.errors.body ? ( <div className="text-sm text-red-600">{formik.errors.body}</div> ) : null} </div> </div> </div> <div className="flex justify-center items-center mb-10 md:mb-20 ml-8"> <button type="submit" disabled={loading} className="py-2 px-4 w-40 font-bold text-white-300 bg-secondary-600 hover:bg-blue-700 rounded-xl disabled:opacity-50 cursor-pointer" > 送信 </button> </div> </div> </form> ); }; export default memo(Form);
これでfrontend側の実装は完了した。
backnedのバリデーションを実装
backnedのバリデーションはozzo-validationを使用した。
コードの書き方は上記で紹介したyupと近い感じで以下のようにして実装できる
package postInquiry import ( "context" "net/http" "os" "time" validation "github.com/go-ozzo/ozzo-validation" "github.com/go-ozzo/ozzo-validation/v4/is" ) type Request struct { Body string `json:"body"` Name string `json:"name"` Email string `json:"email"` } func (r Request) Validate() error { return validation.ValidateStruct(&r, validation.Field( &r.Name, validation.Required.Error("名前の入力は必須です"), validation.RuneLength(1, 100).Error("名前は最大100文字までです"), ), validation.Field( &r.Email, validation.Required.Error("Emailの入力は必須です"), validation.RuneLength(1, 200).Error("ールアドレスは最大200文字までです"), is.Email.Error("メールアドレスを入力して下さい"), ), validation.Field( &r.Body, validation.Required.Error("本文の入力は必須です"), validation.RuneLength(1, 2000).Error("本文は最大2000文字までです"), ), ) } func PostInquiry(w http.ResponseWriter, r *http.Request) { var req Request if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := req.Validate(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
上記を実装してエラーになるRequestパラメータでアクセスすると以下のエラーになる。
これでbackendのバリデーションの実装も完了した。