Ky artikull është i sponsorizuar nga SurveyJS Ekziston një model mendor që shumica e zhvilluesve të React ndajnë pa e diskutuar atë me zë të lartë. Që format supozohen gjithmonë të jenë përbërës. Kjo do të thotë një pirg si:

React Hook Form për gjendjen lokale (rindërrim minimal, regjistrim ergonomik i fushës, ndërveprim imperativ). Zod për vërtetim (korrektësia e hyrjes, vlefshmëria e kufijve, analizimi i sigurt për tipin). Reagon Query për backend: dorëzim, riprovime, caching, sinkronizimi i serverit, e kështu me radhë.

Dhe për shumicën dërrmuese të formave - ekranet tuaja të hyrjes, faqet e cilësimeve, modalet tuaja CRUD - kjo funksionon vërtet mirë. Secila pjesë bën punën e saj, ato kompozojnë pastër, dhe ju mund të kaloni në pjesët e aplikacionit tuaj që në fakt e dallojnë produktin tuaj. Por herë pas here, një formular fillon të grumbullojë gjëra të tilla si rregullat e dukshmërisë që varen nga përgjigjet e mëparshme, ose vlerat e nxjerra që kalojnë nëpër tre fusha. Ndoshta edhe faqe të tëra që duhet të anashkalohen ose të shfaqen bazuar në një total të ekzekutuar. Ju trajtoni kushtëzimin e parë me një useWatch dhe një degë inline, gjë që është në rregull. Pastaj një tjetër. Më pas, po kërkoni superRefine për të koduar rregullat ndërfushash që skema juaj Zod nuk mund t'i shprehë në mënyrën normale. Pastaj, navigimi i hapave fillon të rrjedhë nga logjika e biznesit. Në një moment, ju shikoni atë që keni ndërtuar dhe kuptoni se forma nuk është më në të vërtetë UI. Është më shumë një proces vendimmarrjeje dhe pema përbërëse është pikërisht aty ku ju ka ndodhur ta ruani atë. Këtu mendoj se modeli mendor për format në React prishet, dhe në të vërtetë nuk është faji i askujt. Rafti RHF + Zod është i shkëlqyer në atë për të cilin është krijuar. Çështja është se ne priremi ta përdorim atë përtej pikës ku abstraksionet e tij përputhen me problemin, sepse alternativa kërkon një mënyrë krejtësisht të ndryshme të të menduarit për format. Ky artikull ka të bëjë me atë alternativë. Për ta treguar këtë, ne do të ndërtojmë saktësisht të njëjtën formë me shumë hapa dy herë:

Me React Hook Form + Zod të lidhur me React Query për dorëzim, Me SurveyJS, i cili trajton një formë si të dhëna - një skemë e thjeshtë JSON - në vend të një pemë përbërëse.

Të njëjtat kërkesa, e njëjta logjikë e kushtëzuar, e njëjta thirrje API në fund. Më pas do të përshkruajmë saktësisht se çfarë ka lëvizur dhe çfarë ka mbetur, dhe do të parashtrojmë një mënyrë praktike për të vendosur se cilin model duhet të përdorni dhe kur. Formulari që po ndërtojmë:

Ky formular do të përdorë një rrjedhë me 4 hapa: Hapi 1: Detajet

Emri (kërkohet), Email (i detyrueshëm, format i vlefshëm).

Hapi 2: Porosit

Çmimi për njësi, Sasia, Shkalla e tatimit, Rrjedh: Nëntotali, Taksa, Gjithsej.

Hapi 3: Llogaria dhe komentet

A keni një llogari? (Po/Jo) Nëse po → emri i përdoruesit + fjalëkalimi, të dyja kërkohen. Nëse Jo → email i mbledhur tashmë në hapin 1.

Vlerësimi i kënaqësisë (1–5) Nëse ≥ 4 → pyesni "Çfarë ju pëlqeu?" Nëse ≤ 2 → pyesni "Çfarë mund të përmirësojmë?"

Hapi 4: Rishikoni

Shfaqet vetëm nëse gjithsej >= 100 Dorëzimi përfundimtar.

Kjo nuk është ekstreme. Por është e mjaftueshme për të ekspozuar dallimet arkitekturore. Pjesa 1: E drejtuar nga komponentët (React Hook Form + Zod) Instalimi npm instaloni react-hook-form zod @hookform/resolvers @tanstack/react-query

Skema e Zodit Le të fillojmë me skemën Zod, sepse zakonisht aty vendoset forma e formës. Për dy hapat e parë - detajet personale dhe hyrjet e porosive - gjithçka është e thjeshtë: vargjet e kërkuara, numrat me minimume dhe një numër. Pjesa interesante fillon kur përpiqeni të shprehni rregullat e kushtëzuara.

importoni { z } nga "zod";

export const formSchema = z.object({ firstName: z.string().min(1, "Required"), email: z.string().email("Email i pavlefshëm"), çmimi: z.number().min(0), sasia: z.number().min(1), norma e taksave: z.number(), has Account:(Nr"]Yesum. z.string().optional(), fjalëkalimi: z.string().optional(), kënaqësia: z.number().min(1).max(5), feedback pozitiv: z.string().optional(), përmirësimFeedback: z.string().optional(),}).superRefine((data, {Afsda) =Y=" (!data.username) { ctx.addIssue({ code: "custom", shteg: ["username"], message: "Required" } } if (!data.password || data.password.length < 6) { ctx.addIssue({ code: "password", shtegu]"); }

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kodi: "custom", rruga: ["positiveFeedback"], mesazhi: "Ju lutemi ndajeni atë që ju pëlqeu" }); }

if (data.kënaqësi <= 2 && !data.improvementFeedback) { ctx.addIssue({ kodi: "custom", shteg:["improvementFeedback"], mesazhi: "Ju lutemi na tregoni se çfarë të përmirësojmë" }); }});

lloji i eksportit FormData = z.infer;

Vini re se emri i përdoruesit dhe fjalëkalimi janë shtypur si opsionale() edhe pse ato kërkohen me kusht, sepse skema e nivelit të tipit të Zod përshkruan formën e objektit, jo rregullat që rregullojnë kur fushat kanë rëndësi. Kërkesa e kushtëzuar duhet të jetojë brenda superRefine, e cila funksionon pasi forma është vërtetuar dhe ka akses në objektin e plotë. Kjo ndarje nuk është një e metë; është pikërisht ajo për të cilën mjeti është krijuar: superRefine është vendi ku shkon logjika ndërfushe kur ajo nuk mund të shprehet në vetë strukturën e skemës. Ajo që është gjithashtu e dukshme këtu është ajo që kjo skemë nuk shpreh. Nuk ka koncept të faqeve, asnjë koncept se cilat fusha janë të dukshme në cilën pikë dhe nuk ka koncept të navigimit. E gjithë kjo do të jetojë diku tjetër. Komponenti i Formës

import {useForm, useWatch } nga "react-hook-form";import { zodResolver } nga "@hookform/resolvers/zod";import {useMutation } nga "@tanstack/react-query";import {useState, useMemo } nga "react";importo lloji "FormSche";

const STEPS = ["detaje", "porosi", "llogari", "rishikim"];

shkruani OrderPayload = FormData & { nëntotali: numri; taksa: numri; totali: numri };

funksioni i eksportit RHFMultiStepForm() { const [hapi, setStep] = useState(0);

const mutation = useMutation({ mutationFn: asinkron (ngarkesa: OrderPayload) => { const res = prit fetch("/api/orders", { metoda: "POST", headers: { "Content-Type": "application/json" }, trupi: JSON.stringify(payload), }); nëse (!res.ok) hedh gabim të ri ("Dështoi të dorëzohet"); ktheje res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({ solver: zodResolver(formSchema), defaultValues: { çmimi: 0, sasia: 1, Tatimi: 0.1, kënaqësia: 3, hasAccount: "Jo");} const price = useWatch({ control, emri: "price" }); const quantity = useWatch({ kontrolli, emri: "sasia" }); const taxRate = useWatch({ kontrolli, emri: "taxRate" }); const hasAccount = useWatch({ kontrolli, emri: "hasAccount" }); const satisfaction = useWatch({ control, emri: "satisfaction" }); const subtotal = useMemo(() => (çmimi ?? 0) * (sasia ?? 1), [çmimi, sasia]); const tax = useMemo(() => nëntotal * (taxRate ?? 0), [nëntotali, TaxRate]); const total = useMemo(() => nëntotal + taksa, [nëntotali, taksa]); const onSubmit = (të dhënat: FormData) => mutation.mutate({ ...data, nëntotali, taksa, totali }); const showSubmit = (hapi === 2 && total < 100) || (hapi === 3 && gjithsej >= 100)

ktheje (

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

{step === 1 && ( <>

Nëntotali: {subtotal}
Taksa: {tax}
Totali: {total}
)}

{step === 2 && ( <>

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

{satisfaction >= 4 && (