ບົດຄວາມນີ້ແມ່ນໄດ້ຮັບການສະຫນັບສະຫນູນໂດຍ SurveyJS ມີຕົວແບບທາງດ້ານຈິດໃຈທີ່ຜູ້ພັດທະນາ React ສ່ວນໃຫຍ່ແບ່ງປັນໂດຍບໍ່ເຄີຍສົນທະນາມັນອອກມາດັງໆ. ແບບຟອມນັ້ນຄວນເປັນອົງປະກອບສະເໝີ. ນີ້ຫມາຍຄວາມວ່າ stack ເຊັ່ນ:

ແບບຟອມ React Hook ສໍາລັບລັດທ້ອງຖິ່ນ (ການສະແດງຄືນໃຫມ່ຫນ້ອຍທີ່ສຸດ, ການລົງທະບຽນພາກສະຫນາມ ergonomic, ການໂຕ້ຕອບທີ່ຈໍາເປັນ). Zod ສໍາລັບການກວດສອບຄວາມຖືກຕ້ອງ (ການປ້ອນຂໍ້ມູນທີ່ຖືກຕ້ອງ, ການກວດສອບຂອບເຂດ, ການແຍກປະເພດທີ່ປອດໄພ). React Query ສໍາລັບ backend: ການຍື່ນສະເຫນີ, retry, caching, server sync, ແລະອື່ນໆ.

ແລະສໍາລັບຮູບແບບສ່ວນໃຫຍ່ — ຫນ້າຈໍເຂົ້າສູ່ລະບົບຂອງທ່ານ, ຫນ້າການຕັ້ງຄ່າຂອງທ່ານ, CRUD modals ຂອງທ່ານ — ນີ້ເຮັດວຽກໄດ້ດີຫຼາຍ. ແຕ່ລະຊິ້ນເຮັດວຽກຂອງມັນ, ພວກມັນປະກອບຢ່າງສະອາດ, ແລະເຈົ້າສາມາດກ້າວໄປສູ່ສ່ວນຕ່າງໆຂອງແອັບພລິເຄຊັນຂອງເຈົ້າເຊິ່ງເຮັດໃຫ້ຜະລິດຕະພັນຂອງເຈົ້າແຕກຕ່າງກັນ. ແຕ່ທຸກໆຄັ້ງໃນຂະນະນັ້ນ, ແບບຟອມຈະເລີ່ມສະສົມສິ່ງຕ່າງໆ ເຊັ່ນ: ກົດລະບຽບການເບິ່ງເຫັນທີ່ຂຶ້ນກັບຄຳຕອບກ່ອນໜ້າ, ຫຼືຄ່າທີ່ມາຈາກສາມຊ່ອງຂໍ້ມູນ. ບາງທີແມ່ນແຕ່ຫນ້າທັງຫມົດທີ່ຄວນຈະຖືກຂ້າມຫຼືສະແດງໃຫ້ເຫັນໂດຍອີງໃສ່ຈໍານວນທັງຫມົດທີ່ແລ່ນ. ທ່ານຈັດການເງື່ອນໄຂທໍາອິດກັບ useWatch ແລະສາຂາ inline, ເຊິ່ງດີ. ຫຼັງຈາກນັ້ນ, ອື່ນ. ຫຼັງຈາກນັ້ນ, ທ່ານກໍາລັງເຂົ້າຫາ superRefine ເພື່ອເຂົ້າລະຫັດກົດລະບຽບຂ້າມພາກສະຫນາມທີ່ Zod schema ຂອງທ່ານບໍ່ສາມາດສະແດງອອກໃນແບບປົກກະຕິ. ຈາກນັ້ນ, ຂັ້ນຕອນການນໍາທາງເລີ່ມຮົ່ວໄຫຼຕາມເຫດຜົນທາງທຸລະກິດ. ໃນບາງຈຸດ, ທ່ານເບິ່ງສິ່ງທີ່ທ່ານໄດ້ສ້າງແລະຮັບຮູ້ວ່າແບບຟອມບໍ່ແມ່ນ UI ແທ້ໆ. ມັນເປັນຂະບວນການຕັດສິນໃຈຫຼາຍກວ່າ, ແລະຕົ້ນໄມ້ອົງປະກອບແມ່ນພຽງແຕ່ບ່ອນທີ່ທ່ານໄດ້ເກີດຂຶ້ນເພື່ອເກັບຮັກສາມັນ. ນີ້ແມ່ນບ່ອນທີ່ຂ້ອຍຄິດວ່າຕົວແບບທາງຈິດສໍາລັບແບບຟອມໃນ React ທໍາລາຍ, ແລະມັນກໍ່ບໍ່ມີໃຜຜິດ. RHF + Zod stack ແມ່ນດີເລີດໃນສິ່ງທີ່ມັນຖືກອອກແບບມາສໍາລັບ. ບັນຫາແມ່ນວ່າພວກເຮົາມີແນວໂນ້ມທີ່ຈະສືບຕໍ່ໃຊ້ມັນຜ່ານຈຸດທີ່ບໍ່ມີຕົວຕົນຂອງມັນກົງກັບບັນຫາເພາະວ່າທາງເລືອກດັ່ງກ່າວຮຽກຮ້ອງໃຫ້ມີວິທີການຄິດທີ່ແຕກຕ່າງກັນກ່ຽວກັບຮູບແບບທັງຫມົດ. ບົດຄວາມນີ້ແມ່ນກ່ຽວກັບທາງເລືອກນັ້ນ. ເພື່ອສະແດງສິ່ງນີ້, ພວກເຮົາຈະສ້າງແບບຟອມຫຼາຍຂັ້ນຕອນດຽວກັນສອງຄັ້ງ:

ດ້ວຍແບບຟອມ React Hook + Zod ທີ່ມີສາຍເພື່ອຕອບຄໍາຖາມສໍາລັບການຍື່ນສະເຫນີ, ດ້ວຍ SurveyJS, ເຊິ່ງປະຕິບັດແບບຟອມເປັນຂໍ້ມູນ - ຮູບແບບ JSON ງ່າຍໆ - ແທນທີ່ຈະເປັນຕົ້ນໄມ້ອົງປະກອບ.

ຄວາມຕ້ອງການດຽວກັນ, ເຫດຜົນຕາມເງື່ອນໄຂດຽວກັນ, ການໂທ API ດຽວກັນໃນຕອນທ້າຍຂອງ. ຫຼັງຈາກນັ້ນ, ພວກເຮົາຈະວາງແຜນທີ່ແນ່ນອນວ່າສິ່ງທີ່ຍ້າຍແລະສິ່ງທີ່ຢູ່, ແລະວາງວິທີການປະຕິບັດເພື່ອຕັດສິນໃຈວ່າຕົວແບບໃດທີ່ເຈົ້າຄວນໃຊ້, ແລະເວລາໃດ. ແບບຟອມທີ່ພວກເຮົາສ້າງ:

ແບບຟອມນີ້ຈະໃຊ້ຂັ້ນຕອນ 4 ຂັ້ນຕອນ: ຂັ້ນຕອນທີ 1: ລາຍລະອຽດ

ຊື່ (ຕ້ອງ​ການ​)​, ອີເມວ (ຕ້ອງການ, ຮູບແບບທີ່ຖືກຕ້ອງ).

ຂັ້ນຕອນທີ 2: ຄໍາສັ່ງ

ລາຄາຫົວໜ່ວຍ, ປະລິມານ, ອັດ​ຕາ​ພາ​ສີ​, ໄດ້ມາຈາກ: ທັງໝົດຍ່ອຍ, ພາສີ, ທັງໝົດ.

ຂັ້ນຕອນທີ 3: ບັນຊີ & ຄໍາຕິຊົມ

ເຈົ້າມີບັນຊີບໍ? (ແມ່ນ/ບໍ່) ຖ້າແມ່ນ → ຊື່ຜູ້ໃຊ້ + ລະຫັດຜ່ານ, ທັງສອງຕ້ອງການ. ຖ້າບໍ່ແມ່ນ → ອີເມວຖືກເກັບກຳແລ້ວໃນຂັ້ນຕອນທີ 1.

ຄະແນນຄວາມພໍໃຈ (1–5) ຖ້າ ≥ 4 → ຖາມ “ເຈົ້າມັກຫຍັງ?” ຖ້າ ≤ 2 → ຖາມ “ພວກເຮົາສາມາດປັບປຸງຫຍັງໄດ້?”

ຂັ້ນຕອນທີ 4: ທົບທວນຄືນ

ພຽງແຕ່ປະກົດວ່າຈໍານວນທັງຫມົດ >= 100 ການຍື່ນສະເຫນີສຸດທ້າຍ.

ນີ້ບໍ່ແມ່ນທີ່ສຸດ. ແຕ່ມັນພຽງພໍທີ່ຈະສະແດງຄວາມແຕກຕ່າງທາງສະຖາປັດຕະຍະກໍາ. ສ່ວນທີ 1: Component-Driven (React Hook Form + Zod) ການຕິດຕັ້ງ npm ຕິດຕັ້ງ react-hook-form zod @hookform/resolvers @tanstack/react-query

Zod Schema ໃຫ້ເລີ່ມຕົ້ນດ້ວຍ Zod schema, ເພາະວ່າມັນມັກຈະເປັນບ່ອນທີ່ຮູບຮ່າງຂອງແບບຟອມຖືກສ້າງຕັ້ງຂຶ້ນ. ສໍາລັບສອງຂັ້ນຕອນທໍາອິດ - ລາຍລະອຽດສ່ວນບຸກຄົນແລະການປ້ອນຂໍ້ມູນຄໍາສັ່ງ - ທຸກສິ່ງທຸກຢ່າງແມ່ນກົງໄປກົງມາ: ສະຕິງທີ່ຕ້ອງການ, ຕົວເລກທີ່ມີຕໍາ່ສຸດທີ່, ແລະ enum. ສ່ວນທີ່ຫນ້າສົນໃຈເລີ່ມຕົ້ນເມື່ອທ່ານພະຍາຍາມສະແດງກົດລະບຽບທີ່ມີເງື່ອນໄຂ.

ນໍາເຂົ້າ { z } ຈາກ "zod";

export const formSchema = z.object({ firstName: z.string().min(1, "Required"), email: z.string().email("ອີເມລ໌ບໍ່ຖືກຕ້ອງ"), price: z.number().min(0), quantity: z.number().min(1), taxRate: z.number(), hasAccount: z.enum", "username:" (") ". z.string().optional(), ລະຫັດຜ່ານ: z.string().optional(), ຄວາມພໍໃຈ: z.number().min(1.max(5), positiveFeedback: z.string().optional(), improvementFeedback: z.string().optional(),}).superRefine((data, ctx) => { if (data=hasAccount) =data.hasAccount ctx.addIssue({ code: "custom", path: ["username"], message: "required" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({ code: "custom", path: ["password"], message: "Min}); 6" ຕົວອັກສອນ.

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], message: "Please share what you liked" }); }

ຖ້າ (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ code: "custom", ເສັ້ນທາງ:["improvementFeedback"], ຂໍ້ຄວາມ: "ກະລຸນາບອກພວກເຮົາສິ່ງທີ່ຈະປັບປຸງ" }); } });

ປະເພດສົ່ງອອກ FormData = z.infer ;

ສັງເກດເຫັນວ່າຊື່ຜູ້ໃຊ້ແລະລະຫັດຜ່ານຖືກພິມເປັນທາງເລືອກ () ເຖິງແມ່ນວ່າພວກເຂົາຕ້ອງການຕາມເງື່ອນໄຂເພາະວ່າ schema ລະດັບປະເພດ Zod ອະທິບາຍຮູບຮ່າງຂອງວັດຖຸ, ບໍ່ແມ່ນກົດລະບຽບທີ່ປົກຄອງໃນເວລາທີ່ພາກສະຫນາມສໍາຄັນ. ຂໍ້ກໍານົດທີ່ມີເງື່ອນໄຂຕ້ອງອາໄສຢູ່ພາຍໃນ superRefine, ເຊິ່ງດໍາເນີນການຫຼັງຈາກຮູບຮ່າງຖືກກວດສອບແລະສາມາດເຂົ້າເຖິງວັດຖຸເຕັມ. ການແຍກຕົວນັ້ນບໍ່ແມ່ນຂໍ້ບົກພ່ອງ; ມັນເປັນພຽງແຕ່ສິ່ງທີ່ເຄື່ອງມືໄດ້ຖືກອອກແບບສໍາລັບ: superRefine ແມ່ນບ່ອນທີ່ເຫດຜົນຂ້າມພາກສະຫນາມໄປໃນເວລາທີ່ມັນບໍ່ສາມາດສະແດງອອກໃນໂຄງສ້າງ schema ຕົວຂອງມັນເອງ. ສິ່ງທີ່ໂດດເດັ່ນໃນນີ້ແມ່ນສິ່ງທີ່ schema ນີ້ບໍ່ໄດ້ສະແດງອອກ. ມັນບໍ່ມີແນວຄວາມຄິດຂອງຫນ້າ, ບໍ່ມີແນວຄວາມຄິດຂອງທົ່ງນາທີ່ສັງເກດເຫັນຢູ່ໃນຈຸດໃດ, ແລະບໍ່ມີແນວຄວາມຄິດຂອງການນໍາທາງ. ທັງຫມົດນັ້ນຈະອາໄສຢູ່ບ່ອນອື່ນ. ອົງປະກອບແບບຟອມ

ນໍາເຂົ້າ { useForm, useWatch } ຈາກ "react-hook-form"; ນໍາເຂົ້າ { zodResolver } ຈາກ "@hookform/resolvers/zod"; ນໍາເຂົ້າ { useMutation } ຈາກ "@tanstack/react-query"; ນໍາເຂົ້າ { useState, useMemo } ຈາກ "react"; ນໍາເຂົ້າ { form./schema, ປະເພດ};

const STEPS = ["ລາຍລະອຽດ", "ຄໍາສັ່ງ", "ບັນຊີ", "ທົບທວນ"];

ປະເພດ OrderPayload = FormData & { subtotal: number; tax: ຈໍານວນ; ທັງໝົດ: ຈໍານວນ };

ຟັງຊັນສົ່ງອອກ RHFMultiStepForm() { const [ຂັ້ນຕອນ, setStep] = useState(0);

const mutation = useMutation({ mutationFn: async (ຄ່າຈ່າຍ: OrderPayload) => { const res = ລໍຖ້າ fetch("/api/orders", { ວິທີການ: "POST", headers: { "Content-Type": "application/json" }, ເນື້ອໃນ: JSON.stringify(payload), }); ຖ້າ (!res.ok) ຖິ້ມ Error ໃໝ່ ("ບໍ່ສາມາດສົ່ງ"); ກັບຄືນ res.json(); }, });

const { ລົງທະບຽນ, ຄວບຄຸມ, handleSubmit, formState: { errors }, } = useForm({ solver: zodResolver(formSchema), defaultValues: { price: 0, quantity: 1, taxRate: 0.1, satisfaction: 3, hasAccount,}}); const price = useWatch({ control, name: "price" }); const quantity = useWatch({ການຄວບຄຸມ, ຊື່: "ປະລິມານ" }); const taxRate = useWatch({ control, name: "taxRate" }); const hasAccount = useWatch({ control, name: "hasAccount" }); const ຄວາມພໍໃຈ = useWatch({ການຄວບຄຸມ, ຊື່: "ຄວາມພໍໃຈ" }); const subtotal = useMemo(() => (ລາຄາ ?? 0) * (ປະລິມານ ?? 1), [ລາຄາ, ປະລິມານ]); const tax = useMemo(() => subtotal * (taxRate ?? 0), [subtotal, taxRate]); const total = useMemo(() => subtotal + tax, [subtotal, tax]); const onSubmit = (ຂໍ້ມູນ: FormData) => mutation.mutate({ ...data, subtotal, tax, total }); const showSubmit = (ຂັ້ນຕອນ === 2 && ທັງໝົດ < 100) || (ຂັ້ນຕອນ === 3 && ທັງໝົດ >= 100)

return (

{step === 0 && ( <> )}

{step === 1 && ( <>

ຈຳນວນຍ່ອຍ: {total
ອາກອນ: {tax}
ທັງໝົດ: {total}
)}

{step === 2 && ( <> <ເລືອກ {...register("hasAccount")}>

{hasAccount === "ແມ່ນ" && ( <> <ປ້ອນ {...register("username")} placeholder="Username" /> )}

{ຄວາມພໍໃຈ >= 4 && (