Seda artiklit sponsoreerib SurveyJS On olemas vaimne mudel, mida enamik Reacti arendajaid jagab, ilma et oleks seda kunagi valjusti arutanud. Et vormid peaksid alati olema komponendid. See tähendab virna nagu:

React Hook Form kohaliku oleku jaoks (minimaalne ümberrenderdus, ergonoomiline välja registreerimine, kohustuslik suhtlus). Zod valideerimiseks (sisestuse õigsus, piiride valideerimine, tüübikindel sõelumine). Reageerimispäring taustaprogrammi jaoks: esitamine, korduskatsed, vahemällu salvestamine, serveri sünkroonimine ja nii edasi.

Ja enamiku vormide puhul – sisselogimisekraanid, seadete lehed, CRUD-modaalid – töötab see väga hästi. Iga tükk teeb oma tööd, koostab puhtalt ja saate liikuda rakenduse nende osade juurde, mis teie toodet tegelikult eristavad. Kuid aeg-ajalt hakkab vorm koguma asju, nagu nähtavuse reeglid, mis sõltuvad varasematest vastustest, või tuletatud väärtused, mis liiguvad läbi kolme välja. Võib-olla isegi terveid lehti, mis tuleks jooksva kogusumma põhjal vahele jätta või näidata. Esimest tingimust käsitlete useWatchi ja tekstisisese haruga, mis on hea. Siis teine. Seejärel kasutate superRefine'i, et kodeerida väljadevahelisi reegleid, mida teie Zodi skeem ei suuda tavapärasel viisil väljendada. Seejärel hakkab astmeline navigeerimine äriloogikat lekkima. Mingil hetkel vaatate, mida olete ehitanud, ja mõistate, et vorm pole enam kasutajaliides. See on rohkem otsustusprotsess ja komponentide puu on just seal, kus te selle salvestasite. See on koht, kus ma arvan, et Reacti vormide vaimne mudel laguneb ja see pole tegelikult kellegi süü. RHF + Zod pinu on selle jaoks suurepärane, milleks see oli mõeldud. Probleem on selles, et me kipume seda kasutama kohast, kus selle abstraktsioonid vastavad probleemile, sest alternatiiv nõuab vormidest täiesti erinevat mõtlemisviisi. See artikkel räägib sellest alternatiivist. Selle näitamiseks koostame täpselt sama mitmeastmelise vormi kaks korda:

Kui React Hook Form + Zod on edastamiseks ühendatud React Queryga, SurveyJS-iga, mis käsitleb vormi pigem andmetena – lihtsa JSON-skeemina – kui komponendipuuna.

Samad nõuded, sama tingimuslik loogika, sama API kutse lõpus. Seejärel kaardistame täpselt, mis liikus ja mis jäi, ning koostame praktilise viisi, kuidas otsustada, millist mudelit ja millal kasutada. Vorm, mida me ehitame:

See vorm kasutab 4-astmelist voogu: 1. samm: üksikasjad

Eesnimi (nõutav), E-post (nõutav, kehtiv vorming).

2. samm: tellige

Ühiku hind, Kogus, maksumäär, Tuletatud: Vahesumma, maks, Kokku.

3. samm: konto ja tagasiside

Kas teil on konto? (jah/ei) Kui Jah → kasutajanimi + parool, on mõlemad nõutavad. Kui ei → e-kiri on juba 1. sammus kogutud.

Rahuloluhinnang (1–5) Kui ≥ 4 → küsi "Mis teile meeldis?" Kui ≤ 2 → küsi "Mida saame parandada?"

4. samm: vaadake üle

Kuvatakse ainult siis, kui kokku >= 100 Lõplik esitamine.

See ei ole äärmuslik. Kuid arhitektuuriliste erinevuste paljastamisest piisab. 1. osa: komponendipõhine (reageerimiskonksu vorm + Zod) Paigaldamine npm install react-hook-form zod @hookform/resolvers @tanstack/react-query

Zodi skeem Alustame Zodi skeemiga, sest tavaliselt kujuneb seal vormi kuju. Esimese kahe sammu puhul – isikuandmed ja tellimuse sisestused – on kõik lihtne: nõutavad stringid, numbrid miinimumiga ja loend. Huvitav osa algab siis, kui proovite väljendada tingimuslikke reegleid.

import { z } "zodist";

export const formSchema = z.object({eesnimi: z.string().min(1, "Nõutav"), email: z.string().email("Vigane e-posti aadress"), hind: z.number().min(0), kogus: z.number().min(1), taxRate: z.number(), hasAccount: ["],enum",("].esenum" z.string().optional(), parool: z.string().optional(), rahulolu: z.number().min(1).max(5), positiivneTagasiside: z.string().optional(), parendusTagasiside: z.string().optional(),}).superRefine((andmed, ctxdata) =.=.ha { if (ctxda) =. (!andmed.kasutajanimi) { ctx.addIssue({ kood: "kohandatud", tee: ["kasutajanimi"], sõnum: "nõutav" }); } if (!data.password || data.password.length < 6) { ctx.addIssue({ kood: "kohandatud sõna" , tee:]); } }

if (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ kood: "custom", path: ["positiveFeedback"], sõnum: "Palun jagage, mis teile meeldis" }); }

if (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ kood: "kohandatud", tee:["improvementFeedback"], teade: "Palun öelge meile, mida parandada" }); }});

ekspordi tüüp FormData = z.infer;

Pange tähele, et kasutajanimi ja parool sisestatakse valikuna () ehkki need on tingimuslikult nõutavad, kuna Zodi tüübitaseme skeem kirjeldab objekti kuju, mitte väljade tähtsust reguleerivaid reegleid. Tingimuslik nõue peab asuma sees superRefine, mis käivitatakse pärast kuju kinnitamist ja millel on juurdepääs kogu objektile. See eraldamine ei ole viga; see on just see, milleks tööriist on loodud: superRefine on koht, kus väljadeülene loogika läheb siis, kui seda ei saa väljendada skeemi struktuuris endas. Siin on tähelepanuväärne ka see, mida see skeem ei väljenda. Sellel pole lehtede kontseptsiooni, kontseptsiooni selle kohta, millised väljad millisel hetkel on näha, ega ka navigeerimise kontseptsiooni. Kõik see elab kusagil mujal. Vormi komponent

import { useForm, useWatch } from "react-hook-form";import { zodResolver } from "@hookform/resolvers/zod";import { useMutation } from "@tanstack/react-query";import { useState, useMemo } kohast "react";import failist "Data/schema";

const STEPS = ["detailid", "tellimus", "konto", "ülevaade"];

tüüp TellimusPayload = FormData & { vahesumma: number; maks: number; kokku: arv };

ekspordi funktsioon RHFMultiStepForm() { const [samm, setStep] = useState(0);

const mutatsioon = useMutation({ mutationFn: async (kasutav koormus: OrderPayload) => { const res = oota fetch("/api/orders", { meetod: "POST", päised: { "Content-Type": "application/json" }, keha: JSON.stringify(payload), }); if (!res.ok) throw new Error("Ebaõnnestus esitada"); tagasta res.json(); }, });

const { register, control, handleSubmit, formState: { errors }, } = useForm({ lahendaja: zodResolver(formSchema), defaultValues: { hind: 0, kogus: 1, taxRate: 0.1, rahulolu: 3, hasAccount: "Ei", }, }); const price = useWatch({ juhtelement, nimi: "hind" }); const kogus = useWatch({ juhtelement, nimi: "kogus" }); const taxRate = useWatch({ juhtelement, nimi: "taxRate" }); const hasAccount = useWatch({ juhtelement, nimi: "hasAccount" }); const satisfaction = useWatch({ juhtelement, nimi: "rahulolu" }); const vahesumma = useMemo(() => (hind ?? 0) * (kogus ?? 1), [hind, kogus]); const tax = useMemo(() => vahesumma * (taxRate ?? 0), [vahesumma, taxRate]); const kokku = useMemo(() => vahesumma + maks, [vahesumma, maks]); const onSubmit = (andmed: FormData) => mutation.mutate({ ...andmed, vahesumma, maks, kokku }); const showSubmit = (samm === 2 && kokku < 100) || (samm === 3 && kokku >= 100)

return (

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

{step === 1 && ( <>

Vahesumma: {subtotal}
Maks: {tax}
Kokku: {total}
)}

{step === 2 && ( <>

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

{rahulolu >= 4 && ( )}

{rahulolu <= 2 && (