Atik sa a se yon patwone pa SurveyJS Gen yon modèl mantal pifò devlopè React pataje san yo pa janm diskite sou li byen fò. Fòm sa yo toujou sipoze konpozan. Sa vle di yon pil tankou:

Fòm React Hook pou eta lokal (minim re-rann, enskripsyon jaden ergonomic, entèraksyon enperatif). Zod pou validation (correct antre, validation fwontyè, tip-safe parsing). Reyaji Rekèt pou backend: soumèt, reesye, kachèt, senkronizasyon sèvè, ak sou sa.

Ak pou vas majorite fòm yo - ekran login ou yo, paj paramèt ou yo, modal CRUD ou yo - sa a travay vrèman byen. Chak moso fè travay li, yo konpoze pwòp, epi ou ka deplase sou pati yo nan aplikasyon w lan ki aktyèlman diferansye pwodwi ou. Men, chak fwa yon ti tan, yon fòm kòmanse akimile bagay tankou règ vizibilite ki depann de repons pi bonè, oswa valè sòti ki kaskad nan twa jaden. Petèt menm paj tout antye ki ta dwe sote oswa montre ki baze sou yon total kouri. Ou okipe premye kondisyonèl la ak yon useWatch ak yon branch inline, ki se amann. Lè sa a, yon lòt. Lè sa a, w ap rive jwenn superRefine pou kode règ kwa-champ ke chema Zod ou a pa ka eksprime nan fason nòmal la. Lè sa a, navigasyon etap kòmanse koule lojik biznis. Nan kèk pwen, ou gade nan sa ou te bati epi reyalize ke fòm nan pa reyèlman UI ankò. Li plis nan yon pwosesis desizyon, ak pye bwa a eleman se jis kote ou te rive estoke li. Sa a se kote mwen panse ke modèl mantal la pou fòm nan React kraze, epi li vrèman pa fòt pèsonn. Pile RHF + Zod la ekselan nan sa li te fèt pou. Pwoblèm lan se ke nou gen tandans kontinye sèvi ak li pase pwen kote abstraksyon li yo matche ak pwoblèm nan paske altènatif la mande pou yon fason diferan pou panse sou fòm antyèman. Atik sa a se sou altènatif sa a. Pou montre sa a, nou pral bati egzak menm fòm milti-etap de fwa:

Avèk React Hook Form + Zod branche pou React Query pou soumèt, Avèk SurveyJS, ki trete yon fòm kòm done - yon senp chema JSON - olye ke yon pye bwa eleman.

Menm kondisyon, menm lojik kondisyonèl, menm apèl API nan fen an. Lè sa a, nou pral kat egzakteman sa ki deplase ak sa ki rete, epi mete deyò yon fason pratik yo deside ki modèl ou ta dwe itilize, ak ki lè. Fòm nap konstwi a:

Fòm sa a pral itilize yon koule 4 etap: Etap 1: Detay yo

Premye non (obligatwa), Imèl (obligatwa, fòma valab).

Etap 2: Lòd

pri inite, Kantite, Pousantaj taks, Derive: Sou-total, Taks, Total.

Etap 3: Kont & Feedback

Ou gen yon kont? (Wi/Non) Si Wi → non itilizatè + modpas, tou de obligatwa. Si Non → imel yo deja kolekte nan etap 1.

Evalyasyon Satisfaksyon (1–5) Si ≥ 4 → mande "Kisa ou te renmen?" Si ≤ 2 → mande "Kisa nou ka amelyore?"

Etap 4: Revize

Sèlman parèt si total >= 100 Soumèt final la.

Sa a se pa ekstrèm. Men, li ase yo ekspoze diferans achitekti. Pati 1: Konpozan Kondwi (Fòm Hook Reyaksyon + Zod) Enstalasyon npm enstale react-hook-form zod @hookform/resolvers @tanstack/react-query

Zod Schema Ann kòmanse ak chema Zod la, paske se nòmalman kote fòm fòm lan vin etabli. Pou de premye etap yo - detay pèsonèl ak opinyon lòd - tout bagay se senp: fisèl obligatwa, nimewo ki gen minimòm, ak yon enum. Pati enteresan an kòmanse lè ou eseye eksprime règ kondisyonèl yo.

enpòte {z} soti nan "zod";

ekspòtasyon const formSchema = z.object({ firstName: z.string().min(1, "Oblije"), imèl: z.string().email ("Envalid imèl"), pri: z.number().min(0), kantite: z.number().min(1), taxRate: z.number(), hasAccount:(No"]Y.esum" z.string().opsyonèl(), modpas: z.string().opsyonèl(),satisfaksyon:z.number().min(1).max(5), positiveFeedback: z.string().optional(), improvementFeedback: z.string().optional(),}).superRefine((done, ctx) => {si (data, ctx) => {si (data, ctx) => {si (data, ctx) => {si (data, ctx) => {si (data, ctx). ctx.addIssue({ kòd: "custom", chemen: ["username"], mesaj: "Oblije" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({ kòd: "custom", chemen: ["modpas"], mesaj: "Min 6 karaktè}"});

si (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kòd: "custom", chemen: ["positiveFeedback"], mesaj: "Tanpri pataje sa ou renmen" }); }

si (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue ({ kòd: "custom", chemen:["improvementFeedback"], mesaj: "Tanpri, di nou kisa pou amelyore"}); }});

ekspòtasyon kalite FormData = z.infer;

Remake non itilizatè ak modpas yo tape kòm opsyonèl () menm si yo kondisyonèl obligatwa paske chema nivo kalite Zod la dekri fòm objè a, pa règ ki gouvène lè jaden yo enpòtan. Kondisyon kondisyonèl la gen pou viv andedan superRefine, ki kouri apre yo fin valide fòm nan epi li gen aksè a objè a plen. Separasyon sa a se pa yon defo; se jis pou sa zouti a fèt: superRefine se kote lojik kwa-champ ale lè li pa ka eksprime nan estrikti nan chema tèt li. Sa ki remakab tou isit la se sa chema sa a pa eksprime. Li pa gen okenn konsèp nan paj, pa gen okenn konsèp nan ki jaden yo vizib nan ki pwen, ak pa gen okenn konsèp nan navigasyon. Tout sa pral viv yon lòt kote. Eleman Fòm

enpòte { useForm, useWatch } soti nan "react-hook-form";enpòte { zodResolver } soti nan "@hookform/resolvers/zod"; enpòte { useMutation } soti nan "@tanstack/react-query";enpòte { useState, useMemo } soti nan "react";enpòte { formSchema, ki soti nan "FormDataschema";

const STEPS = ["detay", "lòd", "kont", "revizyon"];

tape OrderPayload = FormData & { subtotal: nimewo; taks: nimewo; total: nimewo };

fonksyon ekspòtasyon RHFMultiStepForm () { const [etap, setStep] = useState (0);

konst mutation = useMutation({ mutationFn: async (chaj: OrderPayload) => { const res = tann chache ("/api/lòd", { metòd: "POST", headers: {"Content-Type": "application/json" }, kò: JSON.stringify (chaj), }); si (!res.ok) voye nouvo Erè ("Echwe pou soumèt"); retounen res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), defaultValues: {pri: 0, kantite: 1, taxRate: 0.1, satisfaksyon: 3, hasAccount: "Non", }, }); pri konst = useWatch ({ kontwòl, non: "pri" }); konst kantite = useWatch ({ kontwòl, non: "kantite" }); const taksRate = useWatch({ kontwòl, non: "taxRate"}); const hasAccount = useWatch({ kontwòl, non: "hasAccount" }); satisfaksyon konstan = useWatch({ kontwòl, non: "satisfaksyon"}); const subtotal = useMemo(() => (pri ?? 0) * (kantite ?? 1), [pri, kantite]); const taks = useMemo(() => subtotal * (taxRate ?? 0), [subtotal, taxRate]); const total = useMemo(() => subtotal + taks, [subtotal, taks]); const onSubmit = (done: FormData) => mutation.mutate({ ...done, subtotal, taks, total }); const showSubmit = (etap === 2 && total <100) || (etap === 3 && total >= 100)

retounen (

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

{etap === 1 && ( <> value="0.05">5%

Subtotal: {subtotal}
Taks: {tax}
Total: {total}
)}

{etap === 2 && ( <>

{hasAccount === "Wi" && ( <> )}

{satisfaksyon >= 4 && (