यो लेख SurveyJS द्वारा प्रायोजित हो त्यहाँ धेरैजसो प्रतिक्रिया विकासकर्ताहरूले यसलाई ठूलो स्वरमा छलफल नगरी साझा गर्ने मानसिक मोडेल हो। त्यो फारमहरू सधैं कम्पोनेन्ट हुन मानिन्छ। यसको अर्थ स्ट्याक जस्तै:

स्थानीय राज्यको लागि प्रतिक्रिया हुक फारम (न्यूनतम पुन: रेन्डर, एर्गोनोमिक क्षेत्र दर्ता, अनिवार्य अन्तरक्रिया)। प्रमाणीकरणका लागि Zod (इनपुट शुद्धता, सीमा प्रमाणीकरण, टाइप-सुरक्षित पार्सिङ)। ब्याकइन्डको लागि प्रतिक्रिया प्रतिक्रिया: सबमिशन, पुन: प्रयास, क्यासिङ, सर्भर सिङ्क, र यस्तै।

र धेरै जसो फारमहरूको लागि - तपाईंको लगइन स्क्रिनहरू, तपाईंको सेटिङहरू पृष्ठहरू, तपाईंको CRUD मोडलहरू - यसले वास्तवमै राम्रोसँग काम गर्दछ। प्रत्येक टुक्राले यसको काम गर्दछ, तिनीहरू सफासँग रचना गर्छन्, र तपाईं आफ्नो अनुप्रयोगको भागहरूमा जान सक्नुहुन्छ जुन वास्तवमा तपाईंको उत्पादनलाई फरक पार्छ। तर प्रत्येक पटक केहि समय मा, एक फारमले पहिलेका जवाफहरूमा निर्भर हुने दृश्यता नियमहरू, वा तीनवटा क्षेत्रहरू मार्फत क्यास्केड गर्ने व्युत्पन्न मानहरू जस्ता चीजहरू जम्मा गर्न थाल्छ। हुनसक्छ सम्पूर्ण पृष्ठहरू जुन छोड्नुपर्छ वा चलिरहेको कुलको आधारमा देखाइन्छ। तपाईंले प्रयोगवाच र इनलाइन शाखाको साथ पहिलो सशर्त ह्यान्डल गर्नुहुन्छ, जुन ठीक छ। त्यसपछि अर्को। त्यसोभए तपाईं क्रस-फिल्ड नियमहरू इन्कोड गर्न सुपररिफाइनको लागि पुग्दै हुनुहुन्छ जुन तपाईंको Zod स्कीमाले सामान्य तरिकामा व्यक्त गर्न सक्दैन। त्यसपछि, चरण नेभिगेसनले व्यापार तर्क चुहाउन थाल्छ। केहि बिन्दुमा, तपाईले के निर्माण गर्नुभएको छ भनेर हेर्नुहुन्छ र महसुस गर्नुहुन्छ कि फारम वास्तवमा UI अब छैन। यो एक निर्णय प्रक्रिया को अधिक हो, र घटक रूख मात्र जहाँ तपाईं यसलाई भण्डारण गर्न भयो। यो जहाँ मलाई लाग्छ प्रतिक्रियामा फारमहरूको लागि मानसिक मोडेल बिग्रन्छ, र यो वास्तवमा कसैको गल्ती होइन। RHF + Zod स्ट्याक यसको लागि डिजाइन गरिएकोमा उत्कृष्ट छ। मुद्दा यो हो कि हामी यसलाई बिन्दुमा प्रयोग गरिरहन्छौं जहाँ यसको अमूर्तताहरू समस्यासँग मेल खान्छ किनभने वैकल्पिकलाई पूर्ण रूपमा रूपहरूको बारेमा सोच्ने फरक तरिका चाहिन्छ। यो लेख त्यो विकल्पको बारेमा हो। यो देखाउनको लागि, हामी ठ्याक्कै उही बहु-चरण फारम दुई पटक निर्माण गर्नेछौं:

प्रतिक्रिया हुक फारम + Zod को साथ सबमिशनको लागि प्रतिक्रिया क्वेरी गर्न वायर्ड, SurveyJS सँग, जसले फारमलाई डेटाको रूपमा व्यवहार गर्छ — एक साधारण JSON स्किमा — कम्पोनेन्ट ट्रीको सट्टा।

एउटै आवश्यकताहरू, एउटै सशर्त तर्क, अन्तमा उही API कल। त्यसोभए हामी के सारियो र के रह्यो भन्ने ठ्याक्कै नक्सा गर्नेछौं, र तपाईले कुन मोडेल र कहिले प्रयोग गर्ने भन्ने निर्णय गर्ने व्यावहारिक तरिका तयार गर्नेछौं। हामीले निर्माण गरिरहेको फारम:

यो फारमले 4-चरण प्रवाह प्रयोग गर्नेछ: चरण 1: विवरणहरू

पहिलो नाम (आवश्यक), इमेल (आवश्यक, मान्य ढाँचा)।

चरण 2: अर्डर

एकाइ मूल्य, मात्रा, कर दर, व्युत्पन्न: उप-योग, कर, कुल।

चरण 3: खाता र प्रतिक्रिया

तपाईंसँग खाता छ? (हो/होइन) यदि हो → प्रयोगकर्ता नाम + पासवर्ड, दुवै आवश्यक छ। यदि छैन भने → इमेल पहिले नै चरण १ मा सङ्कलन गरिएको छ।

सन्तुष्टि मूल्याङ्कन (१–५) यदि ≥ 4 → "तपाईलाई के मन पर्यो?" सोध्नुहोस् यदि ≤ 2 → "हामी के सुधार गर्न सक्छौं?"

चरण 4: समीक्षा गर्नुहोस्

कुल >= १०० भए मात्र देखिन्छ अन्तिम सबमिशन।

यो चरम होइन। तर वास्तुगत भिन्नताहरू उजागर गर्न यो पर्याप्त छ। भाग १: कम्पोनेन्ट-संचालित (प्रतिक्रिया हुक फारम + Zod) स्थापना npm install react-hook-form zod @hookform/resolvers @tanstack/react-query

Zod स्कीमा Zod स्कीमाको साथ सुरु गरौं, किनभने सामान्यतया त्यहाँ फारमको आकार स्थापित हुन्छ। पहिलो दुई चरणहरूको लागि - व्यक्तिगत विवरणहरू र अर्डर इनपुटहरू - सबै कुरा सीधा छ: आवश्यक स्ट्रिङहरू, न्यूनतम संख्याहरू, र एक एनम। चाखलाग्दो भाग सुरु हुन्छ जब तपाइँ सशर्त नियमहरू व्यक्त गर्ने प्रयास गर्नुहुन्छ।

"zod" बाट { z } आयात गर्नुहोस्;

निर्यात const formSchema = z.object({ firstName: z.string().min(1, "आवश्यक"), इमेल: z.string().email("अमान्य इमेल"), मूल्य: z.number().min(0), मात्रा: z.number().min(1), tax दर: z.number().min(1), कर दर: z.number:"[ycount(), छ। "No"]), प्रयोगकर्ता नाम: z.string().optional(), password: z.string().optional(), satisfaction: z.number().min(1).max(5), positive Feedback: z.string().optional(), improvementfeedback: z.string().optional(ef), ine (ct) = (ct) = ( ct }) super (data.hasAccount === "हो") { यदि (!data.username) { ctx.addIssue({ code: "custom", path: ["username"], message: "Required" }); ["पासवर्ड"], सन्देश: "न्यूनतम ६ अक्षरहरू" } });

यदि (data.satisfaction >= 4 && !data.positiveFeedback) { ctx.addIssue({ code: "custom", path: ["positiveFeedback"], सन्देश: "कृपया तपाईलाई मनपर्ने कुरा साझा गर्नुहोस्" }); }

यदि (data.satisfaction <= 2 && !data.improvementFeedback) { ctx.addIssue({ code: "custom", path:["सुधार प्रतिक्रिया"], सन्देश: "कृपया हामीलाई के सुधार गर्ने भन्नुहोस्" }); }});

निर्यात प्रकार FormData = z.infer;

ध्यान दिनुहोस् कि प्रयोगकर्ता नाम र पासवर्ड वैकल्पिक () को रूपमा टाइप गरिएको छ यद्यपि तिनीहरू सशर्त रूपमा आवश्यक छन् किनभने Zod को प्रकार-स्तर स्कीमाले वस्तुको आकार वर्णन गर्दछ, फिल्डहरू महत्त्वपूर्ण हुँदा नियमहरू होइन। सशर्त आवश्यकता सुपररिफाइन भित्र बस्नु पर्छ, जुन आकार मान्य भएपछि चल्छ र पूर्ण वस्तुमा पहुँच छ। त्यो अलगाव एक दोष होइन; यो उपकरणको लागि डिजाइन गरिएको हो: सुपररिफाइन त्यो हो जहाँ क्रस-फिल्ड तर्क जान्छ जब यो स्कीमा संरचनामा व्यक्त गर्न सकिँदैन। यहाँ के पनि उल्लेखनीय छ कि यो योजनाले व्यक्त गर्दैन। यसमा पृष्ठहरूको कुनै अवधारणा छैन, कुन बिन्दुमा कुन क्षेत्रहरू देखिने छन् भन्ने कुनै अवधारणा छैन, र नेभिगेसनको कुनै अवधारणा छैन। ती सबै अरू कतै बस्नेछन्। फारम कम्पोनेन्ट

"react-hook-form" बाट { useForm, useWatch } आयात गर्नुहोस्; "@hookform/resolvers/zod" बाट { zodResolver } आयात गर्नुहोस्; "@tanstack/react-query" बाट { useMutation } आयात गर्नुहोस्; "react" बाट { useState, useMemo } आयात गर्नुहोस्; "react" बाट; maata form बाट आयात गर्नुहोस्; maata form } बाट;

const STEPS = ["विवरण", "अर्डर", "खाता", "समीक्षा"];

टाइप गर्नुहोस् OrderPayload = FormData र { उपकुल: संख्या; कर: नम्बर; कुल: संख्या };

निर्यात प्रकार्य RHFMultiStepForm() { const [step, setStep] = useState(0);

const mutation = useMutation({ mutationFn: async (payload: OrderPayload) => { const res = प्रतिक्षा गर्नुहोस् ("/api/orders", { विधि: "POST", हेडर: { "सामग्री-प्रकार": "application/json" }, मुख्य भाग: JSON.stringify(payload), }); यदि (!res.ok) नयाँ त्रुटि ("पेश गर्न असफल") फ्याँकियो; फिर्ता res.json(); }, });

const { दर्ता, नियन्त्रण, handleSubmit, formState: { त्रुटिहरू }, } = useForm({ समाधानकर्ता: zodResolver(formSchema), पूर्वनिर्धारित मानहरू: { मूल्य: 0, मात्रा: 1, कर दर: 0.1, सन्तुष्टि: 3,}}, सन्तुष्टि: 3,}}; const price = useWatch({ control, name: "price" }); const मात्रा = useWatch({ control, name: "quantity" }); const taxRate = useWatch({ control, name: "taxRate" }); const hasAccount = useWatch({ control, name: "hasAccount" }); const satisfaction = useWatch({ नियन्त्रण, नाम: "सन्तुष्टि" }); const subtotal = useMemo(() => (मूल्य ?? 0) * (मात्रा ?? 1), [मूल्य, मात्रा]); const tax = useMemo(() => subtotal * (कर दर?? 0), [उपयोग, कर दर]); const total = useMemo(() => subtotal + tax, [subtotal, tax]); const onSubmit = (डेटा: FormData) => mutation.mutate({...data, subtotal, tax, total }); const showSubmit = (चरण === 2 && कुल <100) || (चरण === ३ र कुल >= १००)

फिर्ता गर्नुहोस् (

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

{चरण === 1 && ( <> <{...register("taxRate", 5%

उपयोग: {subtotal}
कर: {tax}
कुल: {total}
)}

{चरण === 2 && ( <>

{hasAccount === "हो" && ( <> <इनपुट {...register("username")} placeholder="Username" /> )}

{सन्तुष्टि >= ४ र& (