Þessi grein er styrkt af SurveyJS Það er andlegt líkan sem flestir React verktaki deila án þess að ræða það upphátt. Að form eiga alltaf að vera hluti. Þetta þýðir stafla eins og:

React Hook Eyðublað fyrir staðbundið ríki (lágmarks endurútgáfur, vinnuvistfræðileg sviðsskráning, nauðsynleg samskipti). Zod fyrir staðfestingu (réttmæti inntaks, staðfesting á mörkum, tegundarörugg þáttun). React Query fyrir bakenda: uppgjöf, endurtilraunir, skyndiminni, samstilling netþjóns og svo framvegis.

Og fyrir langflest eyðublöð - innskráningarskjár, stillingasíður þínar, CRUD gerðir - þetta virkar mjög vel. Hvert stykki vinnur sitt, þeir semja hreint og þú getur farið yfir í þá hluta forritsins sem í raun aðgreina vöruna þína. En öðru hvoru byrjar eyðublað að safna hlutum eins og sýnileikareglum sem eru háðar fyrri svörum, eða afleidd gildi sem falla í gegnum þrjú svið. Kannski jafnvel heilar síður sem ætti að sleppa eða sýna miðað við heildarfjölda. Þú höndlar fyrsta skilyrt með useWatch og innbyggðri grein, sem er í lagi. Síðan annað. Þá ertu að ná í superRefine til að umrita þversviðsreglur sem Zod stefið þitt getur ekki tjáð á venjulegan hátt. Síðan byrjar skrefaleiðsögn að leka viðskiptarökfræði. Á einhverjum tímapunkti horfirðu á það sem þú hefur smíðað og áttar þig á því að formið er í raun ekki notendaviðmót lengur. Þetta er meira ákvörðunarferli og íhlutatréð er bara þar sem þú geymdir það. Þetta er þar sem ég held að andlega líkanið fyrir form í React brotni niður og það er í raun engum að kenna. RHF + Zod staflan er frábær í því sem hann var hannaður fyrir. Málið er að við höfum tilhneigingu til að halda áfram að nota það framhjá þeim tímapunkti þar sem útdráttur þess passar við vandamálið vegna þess að valkosturinn krefst annars konar hugsunar um form algjörlega. Þessi grein er um þann valkost. Til að sýna þetta munum við smíða nákvæmlega sama fjölþrepa eyðublaðið tvisvar:

Með React Hook Form + Zod tengt við React Query til framlagningar, Með SurveyJS, sem meðhöndlar form sem gögn - einfalt JSON skema - frekar en íhlutatré.

Sömu kröfur, sama skilyrta rökfræði, sama API kall í lokin. Síðan munum við kortleggja nákvæmlega hvað hreyfðist og hvað varð eftir, og útlista hagnýta leið til að ákveða hvaða líkan þú ættir að nota og hvenær. Eyðublaðið sem við erum að byggja:

Þetta eyðublað mun nota 4 þrepa flæði: Skref 1: Upplýsingar

Fornafn (áskilið), Tölvupóstur (áskilið, gilt snið).

Skref 2: Panta

Einingaverð, Magn, Skatthlutfall, Afleitt: Samtala, Skattur, Samtals.

Skref 3: Reikningur og endurgjöf

Ertu með reikning? (Já/Nei) Ef Já → notandanafn + lykilorð, bæði krafist. Ef nei → tölvupóstur þegar safnað í skrefi 1.

Ánægjueinkunn (1–5) Ef ≥ 4 → spyrðu „Hvað líkaði þér?“ Ef ≤ 2 → spyrðu "Hvað getum við bætt?"

Skref 4: Skoðaðu

Birtist aðeins ef samtals >= 100 Lokaskil.

Þetta er ekki öfgafullt. En það er nóg til að afhjúpa byggingarfræðilegan mun. Hluti 1: Íhlutadrifinn (React Hook Form + Zod) Uppsetning npm setja upp react-hook-form zod @hookform/resolvers @tanstack/react-query

Zod Skema Byrjum á Zod stefinu, því það er venjulega þar sem lögun eyðublaðsins festist. Fyrir fyrstu tvö skrefin - persónulegar upplýsingar og pöntunarinntak - er allt einfalt: nauðsynlegir strengir, tölur með lágmarki og upptalning. Hinn áhugaverði hluti byrjar þegar þú reynir að tjá skilyrtu reglurnar.

flytja inn { z } úr "zod";

export const formSchema = z.object({ fornafn: z.string().min(1, "Áskilið"), netfang: z.string().email("Ógilt netfang"), verð: z.number().min(0), magn: z.number().min(1), skatthlutfall: z.number(), hasAccount: z.enum(No["Já),op notandi "No["Já"). lykilorð: z.string().valfrjálst(), fullnæging: z.number().min(1).max(5), jákvæð viðbrögð: z.string().valfrjálst(), endurbætur: z.string().valfrjálst(),}).superRefine((gögn, ctx) => { if (data.hasAccounter ===) "Já" ctx.addIssue({kóði: "sérsniðin", slóð: ["notandanafn"], skilaboð: "Áskilið" } } if (!data.password || data.addIssue < 6) { ctx.addIssue({kóði: "sérsniðið", slóð: ["lykilorð"], skilaboð: "Lágmark 6 stafir"});

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], skilaboð: "Vinsamlegast deildu því sem þér líkaði" }); }

if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kóða: "sérsniðin", slóð:["improvementFeedback"], skilaboð: "Vinsamlegast segðu okkur hvað við eigum að bæta" }); }});

útflutningsgerð FormData = z.infer;

Taktu eftir að notendanafn og lykilorð eru slegin inn sem valfrjálst() jafnvel þó að þau séu skilyrðisbundin vegna þess að tegundarstigsskema Zod lýsir lögun hlutarins, ekki reglurnar um hvenær reitir skipta máli. Skilyrta krafan verður að búa inni í superRefine, sem keyrir eftir að lögunin hefur verið staðfest og hefur aðgang að öllu hlutnum. Sá aðskilnaður er ekki galli; það er bara það sem tólið er hannað fyrir: superRefine er þar sem þversviðsrökfræði fer þegar það er ekki hægt að tjá hana í skemaskipulaginu sjálfu. Það sem er líka athyglisvert hér er það sem þetta skema tjáir ekki. Það hefur ekki hugmynd um síður, ekkert hugmynd um hvaða svið eru sýnileg á hvaða tímapunkti og ekkert hugtak um flakk. Allt þetta mun lifa annars staðar. Form hluti

flytja inn { useForm, useWatch } frá "react-hook-form"; flytja inn { zodResolver } frá "@hookform/resolvers/zod"; flytja inn { useMutation } frá "@tanstack/react-query"; flytja inn { useState, useMemo } frá "react"; import { formSchema} frá "FormDataSchema};

const STEPS = ["upplýsingar", "pöntun", "reikningur", "endurskoðun"];

type OrderPayload = FormData & { subtotal: number; skattur: tala; samtals: tala };

útflutningsfall RHFMultiStepForm() { const [skref, setStep] = useState(0);

const stökkbreyting = useStökkbreyting({ mutationFn: ósamstilltur (payload: OrderPayload) => { const res = await fetch("/api/orders", { aðferð: "POST", hausar: { "Content-Type": "application/json" }, meginmál: JSON.stringify(hleðsla), }); if (!res.ok) throw new Error("Mistókst að senda inn"); skila res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { price: 0, quantity: 1, taxRate: 0.1, satisfaction: 3, hasAccount: "Nei", }, }); const price = useWatch({ control, name: "price" }); const quantity = useWatch({ control, name: "magn" }); const taxRate = useWatch({ control, name: "taxRate" }); const hasAccount = useWatch({ control, name: "hasAccount" }); const satisfaction = useWatch({ control, name: "satisfaction" }); const subtotal = useMemo(() => (verð ?? 0) * (magn ?? 1), [verð, magn]); const tax = useMemo(() => subtotal * (taxRate ?? 0), [subtotal, taxRate]); const samtals = useMemo(() => subtotal + tax, [subtotal, tax]); const onSubmit = (gögn: FormData) => mutation.mutate({ ...data, subtotal, tax, total }); const showSubmit = (skref === 2 && samtals < 100) || (skref === 3 && samtals >= 100)

skila (

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

{skref === 1 && ( <> .option 5

Samtala: {subtotal}
Skattur: {tax}
Samtals: {total}
)}

{skref === 2 && ( <>

{hasAccount === "Já" && ( <> )}

{ánægja >= 4 && (