यह लेख सर्वेजेएस द्वारा प्रायोजित है एक मानसिक मॉडल है जिसे अधिकांश रिएक्ट डेवलपर्स बिना ज़ोर से चर्चा किए साझा करते हैं। उन रूपों को हमेशा घटक माना जाता है। इसका मतलब है एक ढेर जैसा:

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

और अधिकांश फ़ॉर्म के लिए - आपकी लॉगिन स्क्रीन, आपके सेटिंग पृष्ठ, आपके सीआरयूडी मॉडल - यह वास्तव में अच्छी तरह से काम करता है। प्रत्येक टुकड़ा अपना काम करता है, वे सफाई से रचना करते हैं, और आप अपने एप्लिकेशन के उन हिस्सों पर आगे बढ़ सकते हैं जो वास्तव में आपके उत्पाद को अलग करते हैं। लेकिन समय-समय पर, एक फॉर्म दृश्यता नियमों जैसी चीजें जमा करना शुरू कर देता है जो पहले के उत्तरों पर निर्भर होते हैं, या व्युत्पन्न मान जो तीन क्षेत्रों के माध्यम से कैस्केड होते हैं। शायद संपूर्ण पृष्ठ भी जिन्हें छोड़ दिया जाना चाहिए या कुल योग के आधार पर दिखाया जाना चाहिए। आप पहली सशर्तता को यूज़वॉच और इनलाइन शाखा के साथ संभालते हैं, जो ठीक है। फिर एक और। फिर आप क्रॉस-फील्ड नियमों को एन्कोड करने के लिए सुपररिफाइन तक पहुंच रहे हैं जिन्हें आपका ज़ॉड स्कीमा सामान्य तरीके से व्यक्त नहीं कर सकता है। फिर, चरण नेविगेशन व्यावसायिक तर्क को लीक करना शुरू कर देता है। कुछ बिंदु पर, आप देखते हैं कि आपने क्या बनाया है और महसूस करते हैं कि फॉर्म अब वास्तव में यूआई नहीं है। यह एक निर्णय प्रक्रिया से अधिक है, और घटक वृक्ष वही है जहां आपने इसे संग्रहीत किया था। मुझे लगता है कि यहीं पर रिएक्ट में फॉर्मों का मानसिक मॉडल टूट जाता है, और यह वास्तव में किसी की गलती नहीं है। आरएचएफ + ज़ॉड स्टैक उस उद्देश्य के लिए उत्कृष्ट है जिसके लिए इसे डिज़ाइन किया गया था। मुद्दा यह है कि हम इसे उस बिंदु से आगे उपयोग करना जारी रखते हैं जहां इसके सार समस्या से मेल खाते हैं क्योंकि विकल्प के लिए रूपों के बारे में पूरी तरह से सोचने के एक अलग तरीके की आवश्यकता होती है। यह लेख उस विकल्प के बारे में है. इसे दिखाने के लिए, हम ठीक उसी बहु-चरणीय फ़ॉर्म को दो बार बनाएंगे:

सबमिशन के लिए रिएक्ट हुक फॉर्म + ज़ॉड को रिएक्ट क्वेरी से जोड़कर, सर्वेजेएस के साथ, जो एक फॉर्म को डेटा के रूप में मानता है - एक घटक पेड़ के बजाय एक सरल JSON स्कीमा।

समान आवश्यकताएँ, समान सशर्त तर्क, अंत में समान एपीआई कॉल। फिर हम सटीक रूप से मैप करेंगे कि क्या स्थानांतरित हुआ और क्या रुका, और यह तय करने के लिए एक व्यावहारिक तरीका पेश करेंगे कि आपको किस मॉडल का उपयोग करना चाहिए, और कब। हम जो प्रपत्र बना रहे हैं:

यह फॉर्म 4-चरणीय प्रवाह का उपयोग करेगा: चरण 1: विवरण

प्रथम नाम (आवश्यक), ईमेल (आवश्यक, वैध प्रारूप)।

चरण 2: ऑर्डर करें

इकाई मूल्य, मात्रा, कर की दर, व्युत्पन्न: उप योग, कर, कुल.

चरण 3: खाता और फीडबैक

क्या आपके पास कोई खाता है? (हां/नहीं) यदि हां → उपयोगकर्ता नाम + पासवर्ड, दोनों आवश्यक हैं। यदि नहीं → ईमेल चरण 1 में पहले ही एकत्र कर लिया गया है।

संतुष्टि रेटिंग (1-5) यदि ≥ 4 → पूछें "आपको क्या पसंद आया?" यदि ≤ 2 → पूछें "हम क्या सुधार कर सकते हैं?"

चरण 4: समीक्षा करें

केवल तभी प्रकट होता है जब कुल >= 100 हो अंतिम प्रस्तुतिकरण.

यह अति नहीं है. लेकिन यह वास्तु संबंधी मतभेदों को उजागर करने के लिए पर्याप्त है। भाग 1: घटक-चालित (रिएक्ट हुक फॉर्म + ज़ॉड) स्थापना एनपीएम रिएक्ट-हुक-फॉर्म ज़ोड @हुकफॉर्म/रिज़ॉल्वर @टैनस्टैक/रिएक्शन-क्वेरी इंस्टॉल करें

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

"ज़ोड" से आयात { z };

निर्यात स्थिरांक फॉर्मस्कीमा = z.ऑब्जेक्ट ({पहला नाम: z.string().min(1, "आवश्यक"), ईमेल: z.string().ईमेल ("अमान्य ईमेल"), मूल्य: z.number().min(0), मात्रा: z.number().min(1), कर दर: z.number(), hasAccount: z.enum(["हां", "नहीं"]), उपयोगकर्ता नाम: z.string().वैकल्पिक(), पासवर्ड: z.string().वैकल्पिक(), संतुष्टि: z.number().min(1).max(5), सकारात्मक फीडबैक: z.string().वैकल्पिक(), सुधार फीडबैक: z.string().वैकल्पिक(),}).superRefine((डेटा, ctx) => { if (data.hasAccount === "हां") { if (!data.username) { ctx.addIssue({ कोड: "कस्टम", पथ: ["उपयोगकर्ता नाम"], संदेश: "आवश्यक" }); } यदि (!data.password || data.password.length < 6) { ctx.addIssue({ कोड: "कस्टम", पथ: ["पासवर्ड"], संदेश: "न्यूनतम 6 अक्षर" } });

यदि (डेटा.संतुष्टि >= 4 && !डेटा.पॉजिटिवफीडबैक) { ctx.addIssue({ कोड: "कस्टम", पथ: ["पॉजिटिवफीडबैक"], संदेश: "कृपया जो आपको पसंद आया उसे साझा करें" }); }

अगर (डेटा.संतुष्टि <= 2 && !डेटा.सुधार फीडबैक) { ctx.addIssue({ कोड: "कस्टम", पथ:["सुधार प्रतिक्रिया"], संदेश: "कृपया हमें बताएं कि क्या सुधार करना है" }); }});

निर्यात प्रकार फॉर्मडेटा = z.infer;

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

"react-hook-form" से आयात करें {useForm, useWatch }; "@hookform/resolvers/zod" से आयात करें { zodResolver }; "@tanstack/react-query" से आयात करें {useMutation }; "react" से आयात करें {useState,useMemo };

स्थिरांक चरण = ["विवरण", "आदेश", "खाता", "समीक्षा"];

ऑर्डरपेलोड टाइप करें = फॉर्मडेटा और { उप-योग: संख्या; कर नंबर; कुल गणना };

निर्यात फ़ंक्शन RHFMultiStepForm() { स्थिरांक [चरण, सेटस्टेप] = उपयोगस्टेट(0);

स्थिरांक उत्परिवर्तन = उपयोग उत्परिवर्तन({ उत्परिवर्तनएफएन: एसिंक (पेलोड: ऑर्डरपेलोड) => { स्थिरांक = प्रतीक्षा करें ("/एपीआई/ऑर्डर", { विधि: "पोस्ट", हेडर: { "सामग्री-प्रकार": "एप्लिकेशन/जेसन" }, मुख्य भाग: JSON.stringify(पेलोड), }); यदि (!res.ok) नई त्रुटि फेंकें ("सबमिट करने में विफल"); वापसी res.json(); }, });

स्थिरांक { रजिस्टर, नियंत्रण, हैंडल सबमिट, फॉर्मस्टेट: {त्रुटियां }, } = उपयोगफॉर्म<फॉर्मडेटा>({रिज़ॉल्वर: zodResolver(formSchema), defaultValues: {मूल्य: 0, मात्रा: 1, कर दर: 0.1, संतुष्टि: 3, hasAccount: "नहीं", }, }); स्थिरांक मूल्य = उपयोग देखें({नियंत्रण, नाम: "कीमत" }); स्थिरांक मात्रा = उपयोग देखें({नियंत्रण, नाम: "मात्रा" }); स्थिरांक कर दर = उपयोग देखें({नियंत्रण, नाम: "कर दर" }); const hasAccount = useWatch({ नियंत्रण, नाम: "hasAccount" }); स्थिरांक संतुष्टि = उपयोग देखें({नियंत्रण, नाम: "संतुष्टि" }); स्थिरांक उप-योग = यूज़मेमो(() => (कीमत ?? 0) * (मात्रा ?? 1), [कीमत, मात्रा]); स्थिरांक कर = उपयोग मेमो (() => उप-योग * (कर दर ?? 0), [उप योग, कर दर]); स्थिरांक कुल = यूज़मेमो (() => उप-योग + कर, [उप-योग, कर]); स्थिरांक onSubmit = (डेटा: फॉर्मडेटा) => उत्परिवर्तन.उत्परिवर्तित ({...डेटा, उप-योग, कर, कुल }); स्थिरांक शोसबमिट = (चरण === 2 && कुल < 100) || (चरण === 3 && कुल >= 100)

वापसी (

{step === 0 && ( <> <इनपुट {...रजिस्टर('ईमेल')} प्लेसहोल्डर='ईमेल' /> )}

{चरण === 1 && ( <> <इनपुट प्रकार='नंबर' {...रजिस्टर('कीमत', { वैल्यूअसनंबर: सत्य })} /> <इनपुट प्रकार='नंबर' {...रजिस्टर('मात्रा', { वैल्यूअसनंबर: सत्य })} />

{hasAccount === "हाँ" && ( <> <इनपुट {...रजिस्टर ("उपयोगकर्ता नाम")} प्लेसहोल्डर = "उपयोगकर्ता नाम" /> <इनपुट {...रजिस्टर ("पासवर्ड")} प्लेसहोल्डर = "पासवर्ड" /> )}

<इनपुट प्रकार = "संख्या" {...रजिस्टर ("संतुष्टि", { valueAsNumber: true })} />

{संतुष्टि >= 4 && (