ব্যবসায়িক অ্যাপের জন্য Vue 3 ফর্ম স্থাপত্য: পুনঃব্যবহারযোগ্য প্যাটার্ন
Vue 3 ফর্ম স্থাপত্য ব্যবসায়িক অ্যাপের জন্য: পুনঃব্যবহারযোগ্য ফিল্ড কম্পোনেন্ট, পরিষ্কার ভ্যালিডেশন নিয়ম, এবং প্রতিটি ইনপুটে সার্ভার ত্রুটি দেখানোর ব্যবহারিক উপায়।

কেন বাস্তব ব্যবসায়িক অ্যাপগুলোতে ফর্ম কোড ভেঙে যায়
একটি ব্যবসায়িক অ্যাপে একটি ফর্ম সাধারণত ছোটই থাকে না। এটা শুরু হয় "শুধু কয়েকটি ইনপুট" থেকে, তারপর ডজনখানেক ফিল্ড, শর্তাধীন সেকশন, অনুমতি, এবং ব্যাকএন্ড লজিকের সাথে সিঙ্ক রাখার নিয়মে বেড়ে যায়। কিছু প্রোডাক্ট বদলানোর পরে, ফর্ম কাজ করে—কিন্তু কোড দুর্বল হতে শুরু করে।
Vue 3 ফর্ম স্থাপত্য গুরুত্বপূর্ণ কারণ ফর্মই সেই জায়গা যেখানে "দ্রুত সমাধান" জমে: আরেকটা watcher, আরেকটে বিশেষ ক্ষেত্রে হ্যান্ডলিং, আরেকটা কপি করা কম্পোনেন্ট। আজ এটা কাজ করে, কিন্তু বিশ্বাস করা কঠিন হয়ে যায় এবং পরিবর্তন করা কঠিন হয়ে ওঠে।
সতর্কবার্তাগুলো পরিচিত: পেজ জুড়ে ইনপুট আচরণ বারবার লেখা (লেবেল, ফরম্যাটিং, রিকোয়ার্ড মার্কার, হিন্ট), ত্রুটির অনিয়মিত অবস্থান, ভ্যালিডেশন নিয়মগুলো বিভিন্ন কম্পোনেন্টে ছড়িয়ে থাকা, এবং ব্যাকএন্ড ত্রুটিগুলোকে generic একটা toast-এ রূপান্তর করা যা ব্যবহারকারীকে ঠিক কি ঠিক করতে হবে বলে না।
এই অনিয়মগুলো শুধু কোড স্টাইলের সমস্যা নয়। এগুলো UX সমস্যা সৃষ্টি করে: ব্যবহারকারীরা ফর্ম পুনরায় জমা দেয়, সাপোর্ট টিকট বাড়ে, এবং টিমগুলো ফর্মে ছোঁয়া থেকে বিরত থাকে কারণ কোনো লুকানো এজ-কেসে ভাঙতে পারে।
একটি ভালো সিস্টেম ফর্মগুলোকে সেরা ভাবে 'বোরিং' করে তোলে। একটি পূর্বানুমেয় গঠন থাকলে, আপনি নতুন ফিল্ড যোগ করতে পারবেন, নিয়ম পরিবর্তন করতে পারবেন, এবং সার্ভার রেসপন্স হ্যান্ডল করতে পারবেন সবকিছু রি-ওয়ারায়ার্স না করেই।
আপনি এমন একটি ফর্ম সিস্টেম চাইবেন যা পুনঃব্যবহার দেয় (একটি ফিল্ড সর্বত্র একইভাবে আচরণ করে), স্পষ্টতা দেয় (নিয়ম এবং ত্রুটি হ্যান্ডলিং সহজে রিভিউ করা যায়), পূর্বানুমেয় আচরণ দেয় (touched, dirty, reset, submit) এবং উন্নত ফিডব্যাক দেয় (সার্ভার-সাইড ত্রুটিগুলো ঠিক সেই ইনপুটে দেখায় যা মনোযোগ চাই)। নিচের প্যাটার্নগুলো পুনঃব্যবহারযোগ্য ফিল্ড কম্পোনেন্ট, পাঠযোগ্য ভ্যালিডেশন, এবং সার্ভার ত্রুটিগুলিকে নির্দিষ্ট ইনপুটে ম্যাপ করার দিকে ফোকাস করে।
ফর্ম গঠনের জন্য সহজ মানসিক মডেল
সময়সাপেক্ষভাবে টিকে থাকা একটি ফর্ম হলো পরিষ্কার অংশবিশেষ সহ একটি ছোট সিস্টেম, একটি ইনপুটদের কচরাপুল নয়।
চারটি স্তর ভাবুন যেগুলো এক দিকে কথা বলে: UI ইনপুট সংগ্রহ করে, ফর্ম স্টেট তা সংরক্ষণ করে, ভ্যালিডেশন কী ভুল তা ব্যাখ্যা করে, এবং API লেয়ার লোড ও সেভ করে।
চারটি স্তর (এবং প্রত্যেকটি কি কর্তৃত্ব করে)
- Field UI component: ইনপুট, লেবেল, হিন্ট, এবং ত্রুটির লেখাটি রেন্ডার করে। মান পরিবর্তন ইমিট করে।
- Form state: মানগুলো এবং ত্রুটিগুলো ধরে রাখে (সাথে touched এবং dirty ফ্ল্যাগ)।
- Validation rules: বিশুদ্ধ ফাংশন যা মান পড়ে এবং ত্রুটির মেসেজ রিটার্ন করে।
- API calls: প্রাথমিক ডাটা লোড করা, পরিবর্তন জমা করা, এবং সার্ভার রেসপন্সকে ফিল্ড ত্রুটিতে অনুবাদ করা।
এই পৃথককরণ পরিবর্তনগুলোকে সীমাবদ্ধ রাখে। যখন নতুন একটি অনুরোধ আসে, আপনি একটি লেয়ার আপডেট করলেই অন্যান্যগুলো ভাঙবে না।
কোনটি ফিল্ডে থাকার কথা এবং কোনটি প্যারেন্ট ফর্মে
একটি পুনঃব্যবহারযোগ্য ফিল্ড কম্পোনেন্ট বোরিং হওয়া উচিত। এটি আপনার API, ডাটা মডেল, বা ভ্যালিডেশন নিয়ম জানবে না। এটি শুধুই একটি মান প্রদর্শন করা এবং একটি ত্রুটি দেখানো উচিত।
প্যারেন্ট ফর্ম বাকিটা সমন্বয় করে: কোন ফিল্ড আছে, মান কোথায় থাকে, কখন ভ্যালিডেট করতে হবে, এবং কিভাবে সাবমিট করতে হবে।
একটি সহজ নিয়ম সহায়ক: যদি লজিক অন্য ফিল্ডগুলোর উপর নির্ভর করে (উদাহরণ: "State" কেবল তখনই আবশ্যক যখন "Country" US), সেটা টেনে রাখুন প্যারেন্ট ফর্ম বা ভ্যালিডেশন লেয়ারে, না যে ফিল্ড কম্পোনেন্টের ভেতরে।
যখন নতুন একটি ফিল্ড যোগ করা সত্যিই সামান্য প্রচেষ্টায় হয়, সাধারণত আপনি কেবল ডিফল্ট বা স্কিমা, যেখানে ফিল্ড প্লেস করা হয়েছে সেই মার্কআপ, এবং ফিল্ডের ভ্যালিডেশন নিয়মই টাচ করেন। যদি একটি ইনপুট যোগ করা অপরিহার্যভাবে অপ্রাসঙ্গিক কম্পোনেন্ট জুড়ে পরিবর্তন আনতে বাধ্য করে, তাহলে আপনার সীমানা অস্পষ্ট।
পুনঃব্যবহারযোগ্য ফিল্ড কম্পোনেন্ট: কী স্ট্যান্ডার্ডাইজ করতে হবে
ফর্মগুলো বড় হলে, দ্রুত লাভ পাওয়ার উপায় হলো প্রতিটি ইনপুটকে আলাদা ভাবে না বানানো। ফিল্ড কম্পোনেন্টগুলো পূর্বানুমেয় মনে হওয়া উচিত। এটাই তাদের দ্রুত ব্যবহারযোগ্য করে এবং রিভিউ করা সহজ করে তোলে।
একটি ব্যবহারিক ব্লক সেট:
- BaseField: লেবেল, হিন্ট, ত্রুটির লেখা, স্পেসিং, এবং অ্যাক্সেসিবিলিটি অ্যাট্রিবিউটগুলোর wrapper।
- Input components: TextInput, SelectInput, DateInput, Checkbox, ইত্যাদি। প্রতিটি কন্ট্রোলেই ফোকাস করে।
- FormSection: সম্পর্কিত ফিল্ডগুলোকে টাইটেল, সংক্ষিপ্ত সাহায্য টেক্সট, এবং কনসিস্টেন্ট স্পেসিং দিয়ে গ্রুপ করে।
প্রপগুলোর জন্য, ছোট সেট রাখুন এবং সব জায়গায় এটাকে প্রয়োগ করুন। ৪০টি ফর্ম জুড়ে একটি প্রপ নাম পরিবর্তন করা কষ্টকর।
এইগুলো সাধারণত ততক্ষণে ফল দেয়:
modelValueএবংupdate:modelValuev-model-এর জন্যlabelrequireddisablederror(একক মেসেজ, অথবা আপনি চাইলে অ্যারে)hint
Slots হল যেখানে আপনি নমনীয়তা দেবেন বিনা কনসিস্টেন্সি ভাঙা। BaseField লেআউট স্থিতিশীল রাখুন, কিন্তু ডান দিকের ছোট অ্যাকশন ("Send code") বা লিডিং আইকন-এর মতো ছোট ভ্যারিয়েশনের জন্য slot রাখুন। যদি একটি ভ্যারিয়েশন দুইবারের বেশি আসে, সেটাকে একটি slot করুন বদলে কম্পোনেন্ট কপি করা।
রেন্ডার অর্ডার স্ট্যান্ডার্ডাইজ করুন (লেবেল, কন্ট্রোল, হিন্ট, ত্রুটি)। ব্যবহারকারীরা দ্রুত স্ক্যান করে, টেস্টগুলো সহজ হয়, এবং সার্ভার ত্রুটি ম্যাপিং সরল হয় কারণ প্রতিটি ফিল্ডে ত্রুটি দেখানোর একটিই স্পষ্ট জায়গা থাকে।
ফর্ম স্টেট: values, touched, dirty, এবং reset
ব্যবসায়িক অ্যাপগুলোর বেশিরভাগ ফর্ম বাগ ইনপুটের ব্যাপারে নয়। এগুলো ছড়িয়ে থাকা স্টেট থেকেই আসে: মান এক জায়গায়, ত্রুটি অন্য জায়গায়, এবং একটি রিসেট বাটন কেবল আংশিকভাবে কাজ করে। একটি পরিষ্কার Vue 3 ফর্ম স্থাপত্য একটি কনসিস্টেন্ট স্টেট শেপ দিয়ে শুরু হয়।
প্রথমে, ফিল্ড কী-এর জন্য একটি নামকরণ স্কিম বেছে নিন এবং তাতে স্থির থাকুন। সবচেয়ে সহজ নিয়ম: ফিল্ড কী API পে-লোড কী এর সমান। যদি আপনার সার্ভার first_name আশা করে, আপনার ফর্ম কী হওয়া উচিতও first_name। এই ছোট সিদ্ধান্ত ভ্যালিডেশন, সেভিং, এবং সার্ভার ত্রুটি ম্যাপিং অনেক সহজ করে দেয়।
আপনার ফর্ম স্টেট এক জায়গায় রাখুন (একটি composable, একটি Pinia স্টোর, বা প্যারেন্ট কম্পোনেন্ট), এবং প্রতিটি ফিল্ড সেই স্টেট পড়ে ও লেখে। একটি ফ্ল্যাট স্ট্রাকচার অধিকাংশ স্ক্রিনের জন্য কাজ করে। কেবল তখনই nested যান যখন আপনার API সত্যিই nested।
const state = reactive({
values: { first_name: '', last_name: '', email: '' },
touched: { first_name: false, last_name: false, email: false },
dirty: { first_name: false, last_name: false, email: false },
errors: { first_name: '', last_name: '', email: '' },
defaults: { first_name: '', last_name: '', email: '' }
})
ফ্ল্যাগগুলো নিয়ে একটি ব্যবহারিক ভাবনা:
touched: ব্যবহারকারী কি এই ফিল্ডে ইন্টারেক্ট করেছে?dirty: মান কি ডিফল্ট (বা শেষ সেভ করা) মান থেকে ভিন্ন?errors: এখনই ব্যবহারকারী কী বার্তা দেখতে পাওয়া উচিত?defaults: আমরা কোন মানে রিসেট করবো?
রিসেট আচরণ পূর্বানুমেয় হওয়া উচিত। যখন আপনি একটি বিদ্যমান রেকর্ড লোড করেন, তখন একই সোর্স থেকে values এবং defaults সেট করুন। তারপর reset() defaults-কে values-এ কপি করবে, touched মুছে ফেলবে, dirty মুছে ফেলবে, এবং errors ক্লিয়ার করবে।
উদাহরণ: একটি কাস্টমার প্রোফাইল ফর্ম সার্ভার থেকে email লোড করে। যদি ব্যবহারকারী এটি এডিট করে, dirty.email true হয়ে যায়। যদি তারা Reset ক্লিক করে, ইমেইল লোড করা মানে ফিরে যায় (খালি স্ট্রিং নয়), এবং স্ক্রিন আবার পরিষ্কার দেখায়।
ভ্যালিডেশন নিয়ম যা পাঠযোগ্য থাকে
পাঠযোগ্য ভ্যালিডেশন লাইব্রেরির অধিক নয় বরং আপনি কিভাবে নিয়মগুলো প্রকাশ করেন। যদি আপনি একটি ফিল্ডে ঝটপট দেখে তার নিয়ম বুঝতে পারেন, তাহলে আপনার ফর্ম কোড রক্ষণাবেক্ষণযোগ্য থাকে।
এমন একটি নিয়ম স্টাইল বেছে নিন যা আপনি মানতে পারবেন
বেশিরভাগ টিম নিচের কোন এক পন্থায় স্থির হয়:
- Per-field rules: নিয়মগুলো ফিল্ডের কাছাকাছি থাকে। স্ক্যান করতে সহজ, ছোট থেকে মাঝারি ফর্মের জন্য দুর্দান্ত।
- Schema-based rules: নিয়মগুলো একটি অবজেক্ট বা ফাইলে থাকে। যখন অনেক স্ক্রিন একই মডেল reuse করে তখন ভাল।
- Hybrid: সহজ নিয়মগুলো ফিল্ডের কাছে, শেয়ার বা জটিল নিয়মগুলো কেন্দ্রীয় স্কিমায়।
আপনি যাইই বেছে নিন, নিয়মের নাম এবং মেসেজগুলো predictable রাখুন। কয়েকটি সাধারণ নিয়ম (required, length, format, range) বহু একক-উদাহরণ হেল্পারের চেয়ে ভালো।
নিয়মগুলোকে সহজ ইংরেজির মত লেখুন
একটি ভালো নিয়ম বাক্যসদৃশ হওয়া উচিত: "Email is required and must look like an email." চতুর ওয়ান-লাইনারের থেকে বিরত থাকুন যা উদ্দেশ্য লুকায়।
অধিকাংশ ব্যবসায়িক ফর্মের জন্য, প্রতি ফিল্ডে একবারে একটি মেসেজ রিটার্ন করা (প্রথম ব্যর্থতা) UI-কে শান্ত রাখে এবং ব্যবহারকারীদের দ্রুত সঠিক করতে সাহায্য করে।
সাধারণ নিয়মগুলো যা ব্যবহারকারী-বান্ধব থাকে:
- Required কেবল তখনই যখন ব্যবহারকারী সত্যিই ফিল্ড পূরণ করতে হবে।
- Length বাস্তব সংখ্যা দিয়ে (উদাহরণ: 2 থেকে 50 অক্ষর)।
- Format ইমেইল, ফোন, ZIP-এর জন্য, কিন্তু অত্যন্ত কড়া regex ব্যবহার করা হলে সঠিক ইনপুটও বাতিল করতে পারে—তাও এড়িয়ে চলুন।
- Range যেমন "তারিখ ভবিষ্যতে হবে না" বা "মোট সংখ্যা 1 থেকে 999 এর মধ্যে।"
অ্যাসিঙ্ক চেকগুলো স্পষ্ট করুন
অ্যাসিঙ্ক ভ্যালিডেশন (যেমন "username নেওয়া আছে") বিভ্রান্তিকর হয় যদি তা নিঃশব্দে চালানো হয়।
blur-এ বা ছোট ডিবাউন্সের পর চেক ট্রিগার করুন, একটি স্পষ্ট "Checking..." স্টেট দেখান, এবং ব্যবহারকারী টাইপ করতে থাকলে পুরনো রিকোয়েস্টগুলো ক্যানসেল বা উপেক্ষা করুন।
ভ্যালিডেশন কখন চালানো হবে ঠিক করুন
টাইমিং নিয়মের মতোই গুরুত্বপূর্ণ। একটি ব্যবহারকারী-বান্ধব সেটআপ সাধারণত:
- On change এমন ফিল্ডের জন্য যেগুলো লাইভ ফিডব্যাক থেকে লাভ পায় (যেমন পাসওয়ার্ড স্ট্রেংথ), কিন্তু নরম রাখুন।
- On blur বেশিরভাগ ফিল্ডের জন্য, যাতে ব্যবহারকারী টাইপ করে থাকাকালীন ক্রমাগত ত্রুটিতে ভুগে না।
- On submit সম্পূর্ণ ফর্মের জন্য চূড়ান্ত সেফটি নেট হিসেবে।
সার্ভার ত্রুটিগুলোকে সঠিক ইনপুটে ম্যাপ করা
ক্লায়েন্ট-সাইড চেকস কেবল গল্পের অর্ধেক। ব্যবসায়িক অ্যাপগুলোতে সার্ভার সেভ প্রত্যাখ্যান করে এমন নিয়ম থাকে যা ব্রাউজার জানতেই পারে না: ডুপ্লিকেট, অনুমতি চেক, স্টেলে ডেটা, স্টেট পরিবর্তন, এবং আরও অনেক কিছু। ভালো ফর্ম UX নির্ভর করে সেই রেসপন্সকে স্পষ্ট মেসেজে পরিণত করে সঠিক ইনপুটের পাশে দেখাতে।
ত্রুটিগুলোকে একটি অভ্যন্তরীণ শেপে নর্মালাইজ করুন
ব্যাকএন্ডগুলো সাধারণত ত্রুটি ফরম্যাটে একমত নয়। কেউ একটি অবজেক্ট রিটার্ন করে, কেউ লিস্ট, কেউ nested ম্যাপ যেখানে কী ফিল্ড নাম। যেকোন কিছুই পান, একে এমন একটি অভ্যন্তরীণ শেপে পরিণত করুন যা আপনার ফর্ম রেন্ডার করতে পারে।
// what your form code consumes
{
fieldErrors: { "email": ["Already taken"], "address.street": ["Required"] },
formErrors: ["You do not have permission to edit this customer"]
}
কিছু নিয়ম ধার্য রাখুন:
- ফিল্ড ত্রুটিগুলোকে অ্যারে হিসেবে স্টোর করুন (যদিও কেবল একটি মেসেজই থাকে)।
- বিভিন্ন পাথ স্টাইলগুলোকে একটি স্টাইলে কনভাট করুন (ডট পাথ ঠিক কাজ করে:
address.street)। - নন-ফিল্ড ত্রুটিগুলো আলাদাভাবে
formErrorsহিসেবে রাখুন। - লগিংয়ের জন্য রো সার্ভার পে-লোড রাখুন, কিন্তু সেটা রেন্ডার করবেন না।
সার্ভার পাথগুলোকে আপনার ফিল্ড কী-র সঙ্গে মিলান
ঝামেলার জিনিস হলো সার্ভারের "পাথ" ধারণাকে আপনার ফর্মের ফিল্ড কী-এর সঙ্গে মিলিয়ে দেওয়া। প্রতিটি ফিল্ড কম্পোনেন্টের জন্য কী নির্ধারণ করুন (উদাহরণ: email, profile.phone, contacts.0.type) এবং তাতে স্থির থাকুন।
তারপর একটি ছোট ম্যাপার লিখুন যা সাধারণ কেসগুলো হ্যান্ডেল করে:
address.street(ডট নোটেশন)address[0].street(অ্যারেজের জন্য ব্র্যাকেট)/address/street(JSON Pointer স্টাইল)
নর্মালাইজেশনের পরে, <Field name="address.street" />-কে fieldErrors["address.street"] পড়তে পারা উচিত কোনো স্পেশাল কেস ছাড়া।
প্রয়োজন হলে এলিয়াস সমর্থন করুন। যদি ব্যাকএন্ড customer_email রিটার্ন করে কিন্তু আপনার UI email ব্যবহার করে, normalization-এর সময় { customer_email: "email" } মতো একটি ম্যাপ রাখুন।
ফিল্ড ত্রুটি, ফর্ম-লেভেল ত্রুটি, এবং ফোকাসিং
সব ত্রুটিই একটি ইনপুটের সাথে সম্পর্কিত নয়। যদি সার্ভার বলে "Plan limit reached" বা "Payment required", তা উপরে একটি ফর্ম-লেভেল মেসেজ হিসেবে দেখান।
ফিল্ড-স্পেসিফিক ত্রুটির জন্য, ইনপুটের পাশে মেসেজ দেখান এবং প্রথম সমস্যার দিকে ব্যবহারকারীকে গাইড করুন:
- সার্ভার ত্রুটিগুলো সেট করার পরে,
fieldErrors-এ থাকা প্রথম কী খুঁজে বের করুন যা আপনার রেন্ডার করা ফর্মে আছে। - এটিকে ভিউতে নিয়ে আসুন এবং focus করুন (প্রতিটি ফিল্ডের জন্য একটি ref এবং
nextTickব্যবহার করে)। - ব্যবহারকারী যখন সেই ফিল্ড আবার এডিট করে, সেই ফিল্ডের সার্ভার ত্রুটি ক্লিয়ার করুন।
ধাপে ধাপে: স্থাপত্য একত্র করা
ফর্মগুলো তখনই শান্ত থাকে যখন আপনি আগে থেকেই ঠিক করেন ফর্ম স্টেট, UI, ভ্যালিডেশন, এবং API-এর কাকে কি দায়িত্ব রয়েছে, তারপর কয়েকটি ছোট ফাংশন দিয়ে তাদের সংযুক্ত করেন।
একটি ক্রম যা বেশিরভাগ ব্যবসায়িক অ্যাপের জন্য কাজ করে:
- একটি ফর্ম মডেল এবং স্থিতিশীল ফিল্ড কী দিয়ে শুরু করুন। সেই কীগুলো কম্পোনেন্ট, ভ্যালিডেটর, এবং সার্ভার ত্রুটিগুলির মধ্যে চুক্তি হিসেবে কাজ করবে।
- লেবেল, সাহায্য টেক্সট, রিকোয়ার্ড মার্ক, এবং ত্রুটি প্রদর্শনের জন্য একটি BaseField wrapper তৈরি করুন। ইনপুট কম্পোনেন্টগুলো ছোট ও কনসিস্টেন্ট রাখুন।
- এমন একটি ভ্যালিডেশন লেয়ার যোগ করুন যা একটি ফিল্ডে রান করতে পারে এবং সাবমিটে সবকিছু ভ্যালিডেট করতে পারে।
- API-তে সাবমিট করুন। যদি তা ব্যর্থ হয়, সার্ভার ত্রুটিগুলোকে
{ [fieldKey]: message }-এ অনুবাদ করুন যাতে সঠিক ইনপুট সঠিক মেসেজ দেখায়। - সাকসেস হ্যান্ডলিং আলাদা রাখুন (reset, toast, navigate) যাতে তা কম্পোনেন্ট ও ভ্যালিডেটরগুলিতে লিক না করে।
স্টেটের একটি সহজ শুরু বিন্দু:
const values = reactive({ email: '', name: '', phone: '' })
const touched = reactive({ email: false, name: false, phone: false })
const errors = reactive({}) // { email: '...', name: '...' }
আপনার BaseField label, error, এবং সম্ভবত touched পায়, এবং একটি জায়গায় মেসেজ রেন্ডার করে। প্রতিটি ইনপুট কম্পোনেন্ট শুধুমাত্র binding এবং ইভেন্ট ইমিট করার ব্যাপারে চিন্তা করে।
ভ্যালিডেশনের জন্য, একই কী ব্যবহার করে মডেলের কাছে নিয়ম রাখুন:
const rules = {
email: v => (!v ? 'Email is required' : /@/.test(v) ? '' : 'Enter a valid email'),
name: v => (v.length < 2 ? 'Name is too short' : ''),
}
function validateAll() {
Object.keys(rules).forEach(k => {
const msg = rules[k](values[k])
if (msg) errors[k] = msg
else delete errors[k]
touched[k] = true
})
return Object.keys(errors).length === 0
}
যখন সার্ভার ত্রুটির সঙ্গে রেসপন্ড করে, সেগুলোকে একই কী ব্যবহার করে ম্যাপ করুন। যদি API { "field": "email", "message": "Already taken" } রিটার্ন করে, সেট করুন errors.email = 'Already taken' এবং এটাকে touched হিসেবে চিহ্নিত করুন। যদি ত্রুটিটি গ্লোবাল হয় (যেমন "permission denied"), তা ফর্মের উপরে দেখান।
উদাহরণ সিচুয়েশন: কাস্টমার প্রোফাইল সম্পাদনা
কল্পনা করুন একটি ইনটার্নাল অ্যাডমিন স্ক্রিন যেখানে একটি সাপোর্ট এজেন্ট কাস্টমার প্রোফাইল সম্পাদনা করছে। ফর্মে চারটি ফিল্ড আছে: name, email, phone, এবং role (Customer, Manager, Admin)। এটি ছোট, কিন্তু সাধারণ সমস্যা দেখায়।
ক্লায়েন্ট-সাইড নিয়মগুলো স্পষ্ট হওয়া উচিত:
- Name: আবশ্যক, ন্যূনতম দৈর্ঘ্য।
- Email: আবশ্যক, সঠিক ইমেইল ফরম্যাট।
- Phone: ঐচ্ছিক, কিন্তু পূরণ করলে গ্রহণযোগ্য ফরম্যাট-match করতে হবে।
- Role: আবশ্যক, এবং কখনো কখনো শর্তাধীন (কেবলমাত্র উপযুক্ত অনুমতি থাকলে Admin এসাইন করা যায়)।
একটি কনসিস্টেন্ট কম্পোনেন্ট চুক্তি সাহায্য করে: প্রতিটি ফিল্ড বর্তমান মান, বর্তমান ত্রুটি টেক্সট (যদি থাকে), এবং কিছু boolean যেমন touched এবং disabled পায়। লেবেল, রিকোয়ার্ড মার্কার, স্পেসিং, এবং ত্রুটি স্টাইলিং প্রতিটি স্ক্রিনে পুনরাবিষ্কার করা উচিত নয়।
এখন UX ফ্লো। এজেন্ট ইমেইল এডিট করে, ট্যাব করে, এবং যদি ফরম্যাট ভুল হয় তবে Email-এর নিচে inline মেসেজ দেখে। তারা তা ঠিক করে, Save টিপে, এবং সার্ভার রেসপন্ড করে:
- email already exists: Email-এর নিচে দেখান এবং সেই ফিল্ডকে focus করুন।
- phone invalid: Phone-এর নিচে দেখান।
- permission denied: উপরের দিকে একটি ফর্ম-লেভেল মেসেজ দেখান।
আপনি যদি ত্রুটিগুলোকে ফিল্ড নাম (email, phone, role) দিয়ে কী করে রাখেন, তাহলে ম্যাপিং সহজ। ফিল্ড ত্রুটিগুলো ইনপুটের পাশে যায়, ফর্ম-লেভেল ত্রুটিগুলো একটি ডেডিকেটেড মেসেজ এরিয়ায়।
সাধারণ ভুল এবং কিভাবে এড়াবেন
লজিক এক জায়গায় রাখুন
প্রতিটি স্ক্রিনে ভ্যালিডেশন নিয়ম কপি করা সুবিধাজনক মনে হয় যতক্ষণ না নীতি বদলায় (পাসওয়ার্ড রুল, আবশ্যক ট্যাক্স আইডি, অনুমোদিত ডোমেইন)। নিয়মগুলো কেন্দ্রীভূত রাখুন (স্কিমা, রুলস ফাইল, শেয়ার্ড ফাংশন), এবং ফর্মগুলো একই রুল সেট ব্যবহার করুক।
এছাড়াও নিচু-লেভেল ইনপুটকে অনেক দায়িত্ব দেবেন না। যদি আপনার <TextField> API কল করতে জানে, ত্রুটি থেকে retry করে, এবং সার্ভার ত্রুটি পে-লোড পার্স করে, তাহলে এটা পুনঃব্যবহারযোগ্যতা হারায়। ফিল্ড কম্পোনেন্টগুলো রেন্ডার করুক, মান পরিবর্তন ইমিট করুক, এবং ত্রুটি প্রদর্শন করুক। API কল এবং ম্যাপিং লজিক রাখুন ফর্ম কন্টেইনার বা একটি composable-এ।
আপনি যখন কনসার্নগুলো মিক্স করেন তখন লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ লক্ষ
(উপরের অনুচ্ছেদটি অপ্রয়োজনীয় শব্দের প্রতিরোধ করার জন্য অনুবাদে সরলীকৃত হয়েছে.)
চিন্হিত লক্ষণগুলো যখন কনসার্নগুলো মিক্স হয়:
- একই ভ্যালিডেশন মেসেজ বহু জায়গায় লেখা থাকে।
- একটি ফিল্ড কম্পোনেন্ট API ক্লায়েন্ট ইমপোর্ট করে।
- একটি endpoint পরিবর্তন অনেক অনুপযুক্ত ফর্ম ভাঙে।
- টেস্টিংয়ের জন্য পুরো অ্যাপ মাউন্ট করতে হয় কেবল একটি ইনপুট চেক করার জন্য।
UX ও অ্যাক্সেসিবিলিটি টিপ-ট্রাপ
একটি সাধারণ ত্রুটি ব্যানার যেমন "Something went wrong" যথেষ্ট নয়। মানুষদের জানতে হবে কোন ফিল্ডটি ভুল এবং পরবর্তী কী করা উচিত। গ্লোবাল ব্যানারের জন্য নেটওয়ার্ক ডাউন বা অনুমতি-সংক্রান্ত ব্যর্থতা ব্যবহার করুন, এবং সার্ভার ত্রুটিগুলো নির্দিষ্ট ইনপুটে ম্যাপ করুন যাতে ব্যবহারকারীরা দ্রুত ঠিক করতে পারে।
লোডিং এবং ডাবল-সাবমিট সমস্যা বিভ্রান্তিকর অবস্থা তৈরি করে। সাবমিটিংয়ের সময় সাবমিট বোতাম নিষ্ক্রিয় করুন, এমন ফিল্ডগুলো নিষ্ক্রিয় করুন যা সেভিং চলাকালীন পরিবর্তন করা উচিত নয়, এবং একটি স্পষ্ট ব্যস্ত অবস্থান দেখান। নিশ্চিত করুন যে reset এবং cancel ফর্মকে পরিষ্কারভাবে পুনরুদ্ধার করে।
কাস্টম কম্পোনেন্টগুলোর সঙ্গে অ্যাক্সেসিবিলিটি বেসিকগুলি ছোঁয়া সহজে বাদ পড়ে যায়। কয়েকটি সিদ্ধান্ত বাস্তব ব্যথা প্রতিরোধ করে:
- প্রতিটি ইনপুটের একটি দৃশ্যমান লেবেল আছে (শুধু placeholder নয়)।
- ত্রুটিগুলো সঠিক aria অ্যাট্রিবিউট দিয়ে ফিল্ডগুলোর সাথে সংযুক্ত।
- সাবমিটের পর ফোকাস প্রথম অবৈধ ফিল্ডে চলে যায়।
- নিষ্ক্রিয় ফিল্ডগুলো সত্যিই অ-ইন্টারেকটিভ এবং সঠিকভাবে ঘোষণা করা।
- কীবোর্ড ন্যাভিগেশন পুরো ফর্ম জুড়ে কাজ করে।
দ্রুত চেকলিস্ট এবং পরবর্তী পদক্ষেপ
নতুন একটি ফর্ম শিপ করার আগে একটি দ্রুত চেকলিস্ট চালান। এটি ছোট গ্যাপগুলো ধরবে যা পরে সাপোর্ট টিকট তৈরি করে।
- কি প্রতিটি ফিল্ডের একটি স্থিতিশীল কী আছে যা পে-লোড এবং সার্ভার রেসপন্সের সাথে মেলে (nested path-ও যেমন
billing.address.zip)? - কি আপনি প্রতিটি ফিল্ডকে একটি সঙ্গত ফিল্ড কম্পোনেন্ট API দিয়ে রেন্ডার করতে পারেন (value in, events out, error এবং hint in)?
- সাবমিটে, কি আপনি একবার ভ্যালিডেট করেন, ডাবল-সাবমিট ব্লক করেন, এবং প্রথম অবৈধ ফিল্ডে ফোকাস করে ব্যবহারকারীকে জানান কোথা থেকে শুরু করতে?
- কি আপনি ত্রুটিগুলো সঠিক জায়গায় দেখাতে পারেন: প্রতি ফিল্ডে (ইনপুটের পাশে) এবং ফর্ম লেভেলে (প্রয়োজন হলে সাধারণ মেসেজ)?
- সাকসেসের পরে, কি আপনি স্টেট সঠিকভাবে রিসেট করেন (values, touched, dirty) যাতে পরবর্তী এডিট পরিষ্কারভাবে শুরু হয়?
যদি এক উত্তরও "না", প্রথমে সেটি ঠিক করুন। সবচেয়ে সাধারণ ফর্ম পেইন হলো mismatch: ফিল্ড নামগুলো API থেকে সরিয়ে পড়ে, অথবা সার্ভার ত্রুটিগুলো এমন শেপে আসে যা আপনার UI স্থাপন করতে পারে না।
আপনি যদি internal tools তৈরি করছেন এবং দ্রুত এগোতে চান, AppMaster (appmaster.io) একই প্রাথমিক নীতিগুলো অনুসরণ করে: ফিল্ড UI কনসিস্টেন্ট রাখুন, নিয়ম এবং ওয়ার্কফ্লো কেন্দ্রীভূত করুন, এবং সার্ভার রেসপন্সগুলো সেখানে দেখান যেখানে ব্যবহারকারী অ্যাকশন নিতে পারে।
প্রশ্নোত্তর
Standardize them when you see the same label, hint, required marker, spacing, and error styling repeated across pages. If one “small” change means editing many files, a shared BaseField wrapper and a few consistent input components will save time quickly.
Keep the field component dumb: it renders the label, control, hint, and error, and emits value updates. Put cross-field logic, conditional rules, and anything that depends on other values in the parent form or a validation layer so the field stays reusable.
Use stable keys that match your API payload by default, like first_name or billing.address.zip. This makes validation and server error mapping straightforward because you aren’t constantly translating names between layers.
A simple default is one state object that holds values, errors, touched, dirty, and defaults. When everything reads and writes through the same shape, reset and submit behavior becomes predictable and you avoid “half-reset” bugs.
Set both values and defaults from the same loaded data. Then reset() should copy defaults back into values and clear touched, dirty, and errors so the UI looks clean and matches what the server last returned.
Start with rules as simple functions keyed by the same field names as your form state. Return one clear message per field (first failure) so the UI stays calm and users know what to fix next.
Validate most fields on blur, then validate everything on submit as the final check. Use on-change validation only where it truly helps (like password strength) so users aren’t punished with errors while typing.
Run async checks on blur or after a short debounce, and show an explicit “checking” state. Also cancel or ignore outdated requests so slow responses don’t overwrite newer input and create confusing errors.
Normalize every backend format into one internal shape like { fieldErrors: { key: [messages] }, formErrors: [messages] }. Use one path style (dot notation works well) so a field named address.street can always read fieldErrors['address.street'] without special cases.
Show form-level errors above the form, but put field errors next to the exact input. After a failed submit, focus the first field with an error and clear that field’s server error as soon as the user edits it again.


