Vue 3 অ্যাডমিন প্যানেল স্টেট ম্যানেজমেন্ট: Pinia বনাম লোকাল
Vue 3 অ্যাডমিন প্যানেলের স্টেট ম্যানেজমেন্ট: বাস্তব উদাহরণ—ফিল্টার, ড্রাফট, ট্যাব—দেখে সিদ্ধান্ত নিন Pinia, provide/inject, না লোকাল স্টেট কোনটা ব্যবহার করবেন।

What makes state tricky in admin panels
অ্যাডমিন প্যানেলগুলো স্টেট-ওয়েট মনে হয় কারণ এক স্ক্রিনে অনেক মুভিং পার্ট থাকে। একটি টেবিল শুধু ডেটা নয়—এটাতে সাজানো, ফিল্টার, পেজিনেশন, নির্বাচিত সারি, এবং ব্যবহারকারীরা ভরসা করে এমন “এখন কি হলো?” কনটেক্সট থাকে। বড় ফর্ম, রোল-ভিত্তিক অনুমতি, এবং অ্যাকশনগুলো UI কে কী করতে দেয় তা বদলে দিলে ছোট স্টেট সিদ্ধান্তগুলোর গুরুত্ব বাড়ে।
চ্যালেঞ্জটি মান ভাণ্ডার করা নয়। সমস্যা হলো একাধিক কম্পোনেন্ট যখন একই সত্য দেখতে চায় তখন আচরণ voorspelbaarheid রাখা। যদি একটি ফিল্টার চিপ বলে “Active”, টেবিল, URL, এবং এক্সপোর্ট অ্যাকশন—তিনটিই এক মত হওয়া উচিত। যদি ইউজার একটি রেকর্ড এডিট করে পরে চলে যায়, অ্যাপ তাদের কাজ নিঃশব্দে হারিয়ে ফেললে চলবে না। যদি তারা দুইটি ট্যাবে খুলে রাখে, এক ট্যাব যেন অন্যটিকে ওভাররাইট না করে।
Vue 3-এ সাধারণত তিনটি জায়গার মধ্যে বেছে নিতে হয়:
- Local component state: একটি কম্পোনেন্ট মালিকানাধীন এবং আনমাউন্ট হলে রিসেট করা নিরাপদ।
provide/inject: একটি পেজ বা ফিচার এলাকার মধ্যে ভাগ করা স্টেট, prop drilling ছাড়াই।- Pinia: শেয়ার করা স্টেট যা নেভিগেশন টিকে থাকা উচিত, রুট জুড়ে পুনরায় ব্যবহার করা হবে, এবং ডিবাগ করা সহজ থাকা উচিত।
একটি সহজ ভাবনা: প্রতিটি স্টেট টুকরোকে কোথায় রাখা উচিত জানার জন্য টাইপ, স্কোপ, লাইফটাইম, এবং কনকারেন্সি দেখে নিন যেন এটি সঠিক থাকে, ব্যবহারকারীকে অবাক না করে, এবং স্প্যাগেটি না হয়ে যায়।
নিচের উদাহরণগুলো তিনটি সাধারণ অ্যাডমিন সমস্যা ধরেছে: ফিল্টার ও টেবিল (কী টিকে থাকবে ও কী রিসেট হবে), ড্রাফট ও আনসেভড এডিট (ভরসাযোগ্য ফর্ম), এবং মাল্টি-ট্যাব এডিটিং (স্টেট কোলাইজন এড়ানো)।
A simple way to classify state before choosing a tool
স্টেট নিয়ে তর্ক তখনই সহজ হয় যখন আপনি টুল নিয়ে তর্ক করা বন্ধ করে প্রথমে স্টেটের প্রকৃতি নাম দেন। বিভিন্ন ধরনের স্টেট ভিন্নভাবে আচরণ করে, এবং এগুলো মিশালে অদ্ভুত বাগ তৈরি হয়।
প্রায়োগিক ভাগ:
- UI state: টগল, খোলা ডায়ালগ, নির্বাচিত সারি, সক্রিয় ট্যাব, সর্ট অর্ডার।
- Server state: API রেসপন্স, লোডিং ফ্ল্যাগ, এরর, শেষ রিফ্রেশের সময়।
- Form state: ফিল্ড ভ্যালু, ভ্যালিডেশন এরর, ডার্টি ফ্ল্যাগ, আনসেভড ড্রাফট।
- Cross-screen state: যা একাধিক রুট পড়বে বা পরিবর্তন করবে (চলতি ওয়ার্কস্পেস, শেয়ার্ড পারমিশন)।
তারপর স্কোপ নির্ধারণ করুন। আজ কোথায় স্টেট ব্যবহার হচ্ছে তা জিজ্ঞেস করুন, ভবিষ্যতে কোথায় ব্যবহার হতে পারে না। যদি এটা শুধুমাত্র একটি টেবিল কম্পোনেন্টের মধ্যে গুরুত্বপূর্ণ হয়, লোকাল স্টেট সাধারণত যথেষ্ট। যদি একই পেজের দুইটি সিব্লিং কম্পোনেন্ট এটি লাগে, তাহলে পেজ-লেভেল শেয়ারিং সমস্যা। যদি একাধিক রুটে লাগে, তখন আপনি শেয়ার্ড অ্যাপ স্টেট অঞ্চলে আছেন।
পরবর্তী হলো লাইফটাইম। কিছু স্টেট ড্রয়ার বন্ধ হলে রিসেট হওয়া উচিত। অন্য কিছু নেভিগেশনে টিকে থাকা উচিত (ফিল্টার যদি রেকর্ডে ক্লিক করে ফিরে এলে টিকে থাকে)। কিছু রিলোডেও টিকে থাকা উচিত (একটি দীর্ঘ ড্রাফট যা ব্যবহারকারী পরে ফিরে আসে)। তিনটিকেই একইভাবে আচরণ করলে ফিল্টার হঠাৎ রিসেট হয়ে যায় বা ড্রাফট হারিয়ে যায়—এটাই সমস্যার মূল।
সবশেষে, কনকারেন্সি চেক করুন। অ্যাডমিন প্যানেল দ্রুত এজ কেসে পড়ে: ইউজার একই রেকর্ড দুই ট্যাবে খোলে, ব্যাকগ্রাউন্ড রিফ্রেশ একটি সারি আপডেট করে যখন একটি ফর্ম ডার্টি, বা দুইটি এডিটর সেভ করতে race করে।
উদাহরণ: একটি “Users” স্ক্রিন যেখানে ফিল্টার, টেবিল, এবং একটি এডিট ড্রয়ার আছে। ফিল্টারগুলো UI state এবং পেজ লাইফটাইম। সারিগুলো সার্ভার স্টেট। ড্রয়ার ফিল্ডগুলো ফর্ম স্টেট। একই ইউজার যদি দুই ট্যাবে এডিট করা হয়, আপনাকে স্পষ্ট কনকারেন্সি সিদ্ধান্ত নিতে হবে: ব্লক, মার্জ, না সতর্ক করা।
একবার আপনি স্টেটকে টাইপ, স্কোপ, লাইফটাইম, এবং কনকারেন্সি দিয়ে লেবেল করতে পারলেই টুলের পছন্দ (লোকাল, provide/inject, বা Pinia) সাধারণত অনেক পরিষ্কার হয়ে যায়।
How to choose: a decision process that holds up
ভাল স্টেট পছন্দ শুরু হয় একটি অভ্যাস দিয়ে: কোনো টুল বেছে নেবার আগে স্টেটটি সহজ কথায় বর্ণনা করুন। অ্যাডমিন প্যানেলগুলো টেবিল, ফিল্টার, বড় ফর্ম, এবং রেকর্ডের মধ্যে নেভিগেশন মিশিয়ে দেয়, তাই এমনকি “ছোট” স্টেটও বাগের উৎস হতে পারে।
A 5-step decision process
-
Who needs the state?
- একটাই কম্পোনেন্ট: লোকালেই রাখুন।
- এক পেজের মধ্যে কয়েকটি কম্পোনেন্ট:
provide/injectবিবেচনা করুন। - একাধিক রুট: Pinia ভাবুন।
ফিল্টার ভালো উদাহরণ। যদি ফিল্টার শুধু একটি টেবিলকে প্রভাবিত করে, লোকাল স্টেট ঠিক আছে। যদি ফিল্টার একটি হেডার কম্পোনেন্টে থাকে কিন্তু নিচের টেবিল চালায়, পেজ-স্কোপড শেয়ারিং (প্রায়ই
provide/inject) পরিষ্কার রাখে। -
How long must it live?
- যদি এটি কম্পোনেন্ট আনমাউন্ট হলে মুছে যেতে পারে, লোকাল স্টেট আদর্শ।
- যদি রুট পরিবর্তনের পর টিকে থাকতে হবে, Pinia বেশিরভাগ সময় ভাল।
- যদি রিলোডেও টিকে থাকতে হবে, তাহলে persistence (storage) লাগবে, স্টেট কোথায় রাখছেন তার নির্বিশেষে।
ড্রাফটের জন্য এটা সবচেয়ে গুরুত্বপূর্ণ। আনসেভড এডিট বিশ্বাস-সংবেদনশীল: মানুষ আশা করে তারা ক্লিক করে অন্যত্র গেলে ড্রাফট থাকবে।
-
Should it be shared across browser tabs or isolated per tab?
মাল্টি-ট্যাব এডিটিং যেখানে বাগ লুকায়। যদি প্রতিটি ট্যাবে আলাদা ড্রাফট থাকা উচিত, একক গ্লোবাল সিঙ্গলটন এড়ান। রেকর্ড ID দিয়ে কীড স্টেট বা পেজ-স্কোপড স্টেট পছন্দ করুন যেন এক ট্যাব অন্যটিকে ওভাররাইট না করে।
-
Pick the simplest option that fits.
প্রথমে লোকাল দিয়ে শুরু করুন। কেবল তখনই ওপরে উঠুন যখন প্রকৃত ব্যথা অনুভব করেন: prop drilling, লগিক নকল হওয়া, বা কঠিন-রেপ্রোডিউসেবল রিসেট।
-
Confirm your debugging needs.
যদি আপনাকে পরিবর্তনগুলোর একটি পরিষ্কার, ইনস্পেক্টেবল ভিউ দরকার হয়, Pinia-র কেন্দ্রীভূত অ্যাকশন ও স্টেট ইনসপেকশন ঘণ্টা বাঁচাতে পারে। যদি স্টেট ক্ষণস্থায়ী ও সহজ হয়, লোকাল স্টেট পড়তে সহজ।
Local component state: when it’s enough
লোকাল স্টেটই ডিফল্ট যখন ডেটা শুধু এক কম্পোনেন্টে এক পেজেই জরুরি। অনেক সময় লোকাল না করে ওভারবিল্ড করে আপনি এক স্টোর তৈরি করে নিবেন যেটা মাসখানেক ধরে মেইনটেইন করা কষ্টকর।
একটি স্পষ্ট মানানসই হলো একটি একক টেবিল যার নিজস্ব ফিল্টার আছে। যদি ফিল্টারগুলো শুধু ওই টেবিলকেই প্রভাবিত করে (উদাহরণ: Users তালিকা) এবং অন্য কিছু এদের ওপর নির্ভর করে না, তাহলে টেবিল কম্পোনেন্টের ভিতরে ref ভ্যালু হিসেবে রাখুন। একইভাবে ছোট UI স্টেট যেমন “modal খোলা?”, “কোন সারি এডিট করা হচ্ছে?”, এবং “কতোটা আইটেম এখন নির্বাচিত?” লোকালেই রাখুন।
যা কম্পিউট করা যায় তা স্টোর না করে কম্পিউটেড হিসেবে রাখুন। “Active filters (3)” ব্যাজটি বর্তমান ফিল্টার ভ্যালু থেকে ডেরাইভ করুন। সর্ট লেবেল, ফরম্যাটেড সারাংশ, এবং “can save” ফ্ল্যাগও computed হওয়া ভালো যাতে এগুলো স্বয়ংক্রিয়ভাবে সিঙ্ক থাকে।
রিসেট রুলগুলো সরঞ্জামের চেয়ে বেশি গুরুত্বপূর্ণ। সিদ্ধান্ত নিন কী রিসেট হবে রুট পরিবর্তনে (সাধারণত সবকিছু), এবং কী থাকবে যখন ব্যবহারকারী একই পেজে ভিউ পরিবর্তন করে (আপনি হয়ত ফিল্টার রাখবেন কিন্তু অস্থায়ী সিলেকশন ক্লিয়ার করবেন যাতে অপ্রত্যাশিত ব্যাচ অ্যাকশন না ঘটে)।
লোকাল কম্পোনেন্ট স্টেট সাধারণত যথেষ্ট যখন:
- স্টেট একটা উইজেটকে প্রভাবিত করে (একটি ফর্ম, একটি টেবিল, একটি মডাল)।
- অন্য কোন স্ক্রিন এটি পড়বে বা বদলাবে না।
- আপনি 1–2 কম্পোনেন্টের মধ্যে এটি ধরে রাখতে পারেন কোন দীর্ঘ prop থ্রেড ছাড়া।
- আপনি এর রিসেট আচরণ এক বাক্যে বর্ণনা করতে পারেন।
প্রধান সীমা হলো গভীরতা। যখন একই স্টেট কয়েকটি নেস্টেড কম্পোনেন্টে থ্রেড করা শুরু করে, লোকাল স্টেট prop drilling-এ পরিণত হয়, এবং সেটা সাধারণত provide/inject বা স্টোরের সংকেত।
provide/inject: sharing state within a page or feature area
provide/inject লোকাল স্টেট এবং পূর্ণ স্টোরের মাঝামাঝি অবস্থানে থাকে। একটি প্যারেন্ট তার নিচের সবকিছুতে ভ্যালু প্রদান করে, এবং নেস্টেড কম্পোনেন্টগুলো prop drilling ছাড়াই সেগুলো ইনজেক্ট করে। অ্যাডমিন প্যানেলে এটি খুব ভালো ফিট যখন স্টেটটি একটা স্ক্রিন বা ফিচার এলাকায় অন্তর্ভুক্ত থাকে, পুরো অ্যাপে নয়।
একটি সাধারণ প্যাটার্ন হলো একটি পেজ শেল যা স্টেটটি মালিকানাধীন রাখে এবং ছোট কম্পোনেন্টগুলো তা ব্যবহার করে: একটি ফিল্টার বার, টেবিল, বাল্ক অ্যাকশন টুলবার, ডিটেইলস ড্রয়ার, এবং একটি “আনসেভড চেঞ্জস” ব্যানার। শেলটি একটি ছোট রিএ্যাকটিভ সারফেস প্রদান করতে পারে যেমন একটি filters অবজেক্ট, draftStatus অবজেক্ট (dirty, saving, error), এবং কয়েকটি রিড-অনলি ফ্ল্যাগ (যেমন পারমিশন-ভিত্তিক isReadOnly)।
What to provide (keep it small)
সবকিছু প্রদান করলে আপনি মূলত একটি স্টোর বানিয়ে ফেলছেন কম স্ট্রাকচারে। শুধুমাত্র যেটা কয়েকটি চাইল্ড সত্যিই চায় তাই প্রদান করুন। ফিল্টার একটি ক্লাসিক উদাহরণ: টেবিল, চিপ, এক্সপোর্ট অ্যাকশন, এবং পেজিনেশন সবকে সিঙ্কে রাখতে হলে একটিই সোর্স অফ ট্রুথ শেয়ার করা ভালো, পরিবর্তে প্রপস ও ইভেন্ট জোগাড় করার থেকে।
Clarity and pitfalls
সবচেয়ে বড় ঝুঁকি হলো লুকানো ডিপেন্ডেন্সি: একটি চাইল্ড “জাস্ট ওয়ার্কস” কারণ উপরে কিছু প্রদান করেছে, এবং পরে জানা কঠিন হয় আপডেটগুলো কোথা থেকে আসছে।
পড়তে ও টেস্ট করতে সুবিধাজনক রাখতে ইনজেকশনের নামগুলো স্পষ্ট রাখুন (আশিক সময় constants বা Symbols ব্যবহার করে)। শুধু মিউটেবল অবজেক্ট দেওয়ার বদলে অ্যাকশন দেওয়াই ভালো। একটি ছোট API যেমন setFilter, markDirty, এবং resetDraft মালিকানা এবং অনুমোদিত পরিবর্তনগুলো স্পষ্ট করে।
Pinia: shared state and predictable updates across screens
Pinia তখনই উজ্জ্বল হয় যখন একই স্টেট রুট জুড়ে কনসিস্টেন্ট থাকতে হবে এবং বিভিন্ন কম্পোনেন্ট/পেজে ব্যবহার হবে। অ্যাডমিন প্যানেলে সাধারণত এর অর্থ হলো বর্তমান ইউজার, তারা কী করতে পারে (permissions), কোন organization/workspace সিলেক্ট করা আছে, এবং অ্যাপ-লেভেল সেটিংস। প্রতিটি স্ক্রিনে এগুলো পুনরায় ইমপ্লিমেন্ট করলে কষ্ট বাড়ে।
একটি স্টোর ভাল কারণ এটি একটি জায়গা দেয় শেয়ার করা স্টেট পড়তে ও আপডেট করতে। প্রপস পাস করার পরিবর্তে যেখানে দরকার সেইখানেই স্টোর ইমপোর্ট করে ব্যবহার করুন। তালিকা থেকে ডিটেইলে গেলে বাকি UI একই নির্বাচিত org, পারমিশন, ও সেটিংসে প্রতিক্রিয়া দিতে পারে।
Why Pinia feels easier to maintain
Pinia একটি সরল স্ট্রাকচার চাপায়: state কাঁচা ভ্যালু, getters ডেরাইভড ভ্যালু, এবং actions আপডেটের জন্য। অ্যাডমিন UI-তে ওই স্ট্রাকচার “কুইক ফিক্স”গুলোকে ছড়িয়ে পড়া মিউটেশন বানার থেকে রোধ করে।
যদি canEditUsers চলতি রোলে ও একটি ফিচার ফ্ল্যাগের উপর নির্ভর করে, সেটা একটি getter-এ রাখুন। যদি org বদলাতে ক্যাশ ক্লিয়ার করতে হয় ও ন্যাভিগেশন রিলোড করতে হয়, সেই সিকোয়েন্সটি action-এ রাখুন। ফলে কম রহস্যময় watcher ও “এটাই কেন বদলাল?” মুহূর্ত থাকবে।
Pinia Vue DevTools-র সাথেও ভালো কাজ করে। কোনো বাগ হলে স্টোর স্টেট ইনস্পেক্ট করে দেখা সহজ যে কোন action রান করেছে, বনাম র্যাণ্ডমভাবে তৈরি রিএ্যাক্টিভ অবজেক্ট খুঁজে বেড়ানো।
Avoid the dumping-ground store
একটা গ্লোবাল স্টোর প্রথমে সাজানো মনে হয়, তারপর এটা junk drawer হয়ে যায়। Pinia-র জন্য ভাল ক্যান্ডিডেট হলো সত্যিই শেয়ার করা কনসার্নগুলো—ইউজার identity ও permissions, নির্বাচিত workspace, ফিচার ফ্ল্যাগ, এবং একাধিক স্ক্রিনে ব্যবহৃত রেফারেন্স ডাটা।
পেজ-ওনলি কনসার্ন (যেমন একটি ফর্মের অস্থায়ী ইনপুট) লোকালেই থাকা উচিত যদি একাধিক রুট সত্যিই না লাগে।
Example 1: filters and tables without turning everything into a store
ধরুন একটি Orders পেজ: একটি টেবিল, ফিল্টার (স্ট্যাটাস, তারিখ পরিসর, কাস্টমার), পেজিনেশন, এবং একটি সাইড প্যানেল যা নির্বাচিত অর্ডার প্রিভিউ করে। দ্রুতই জটিলতা বাড়ে কারণ সব ফিল্টার আর টেবিল সেটিংস গ্লোবাল স্টোরে ঢুকিয়ে দেওয়ার লোভ থাকে।
সরলভাবে বেছে নিন কি স্মরণযোগ্য এবং কোথায়:
- Memory only (local or provide/inject): পেজ ত্যাগ করলে রিসেট হবে। ডিসপোজেবল স্টেটের জন্য উপযুক্ত।
- Query params: শেয়ারযোগ্য এবং রিলোডে টিকে থাকে। ফিল্টার ও পেজিনেশন যারা কপি করবে তাদের জন্য ভাল।
- Pinia: নেভিগেশনের সময় টিকে থাকে। “যেভাবে আমি তালিকা ছেড়ে গিয়েছিলাম ঠিক সেভাবেই ফিরে পেতে চাই” এর জন্য ভালো।
তারপর ইমপ্লিমেন্টেশন সাধারণত অনুসরণ করে:
যদি কেউও আশা না করে সেটিংস নেভিগেশন টেকসই হবে, তাহলে filters, sort, page, এবং pageSize Orders পেজ কম্পোনেন্টে রাখুন এবং ঐ পেজ ফেচ ট্রিগার করুক। যদি টুলবার, টেবিল, এবং প্রিভিউ প্যানেল একই মডেল দরকার করে এবং প্রপ ড্রিলিং বেড়েছে, তালিকা মডেলকে পেজ শেলে নিয়ে এসে provide/inject দিয়ে শেয়ার করুন। আর যদি তালিকাটি রুট জুড়ে টিকে থাকা চাইলে (অর্ডার খুলে অন্যথায় গিয়ে ফিরে এলে) Pinia ভাল পছন্দ।
একটি ব্যবহারিক রুল: প্রথমে লোকাল, কিছু চাইল্ড কম্পোনেন্টে একই মডেল লাগলে provide/inject-এ উঠান, এবং কেবল তখনই Pinia নিন যখন আপনাকে আসলেই ক্রস-রুট পার্সিস্টেন্স দরকার।
Example 2: drafts and unsaved edits (forms people trust)
ধরুন একটি সাপোর্ট এজেন্ট কাস্টমার রেকর্ড এডিট করছে: কন্ট্যাক্ট ডিটেইল, বিলিং ইনফো, এবং ইনটারনাল নোটস। তারা ব্যাহত হয়, স্ক্রিন বদলায়, তারপর ফিরে আসে। যদি ফর্ম তাদের কাজ ভুলে যায় বা অর্ধেক সেভ ডেটা সেভ করে দেয়, বিশ্বাস চলে যায়।
ড্রাফটের জন্য তিনটি জিনিস আলাদা করুন: শেষ সেভ করা রেকর্ড, ইউজারের স্টেজড এডিট, এবং UI-ওনলি স্টেট যেমন ভ্যালিডেশন এরর।
Local state: staged edits with clear dirty rules
যদি এডিট স্ক্রিন নিজেই সীমাবদ্ধ থাকে, লোকাল কম্পোনেন্ট স্টেট বেশিরভাগ সময় নিরাপদ। রেকর্ড লোড করে একটি draft ক্লোন রাখুন, isDirty ট্র্যাক করুন (বা ফিল্ড-লেভেল ডার্টি মানচিত্র), এবং এররগুলো ফর্ম কন্ট্রোলের পাশে রাখুন।
একটি সহজ ফ্লো: রেকর্ড লোড, ড্রাফটে ক্লোন, ড্রাফট এডিট, এবং কেবল ইউজার Save ক্লিক করলে সেভ রিকোয়েস্ট পাঠান। Cancel ড্রাফট ড্রপ করে রিলোড করুক।
provide/inject: one draft shared across nested sections
অ্যাডমিন ফর্ম প্রায়ই ট্যাব বা প্যানেলে বিভক্ত থাকে (Profile, Addresses, Permissions)। provide/inject দিয়ে একটি ড্রাফট মodel রাখা যায় এবং ছোট API যেমন updateField(), resetDraft(), এবং validateSection() প্রদত্ত করে প্রতিটি সেকশন একই ড্রাফট পড়ে ও লেখে prop পাস না করে।
When Pinia helps with drafts
Pinia দরকারী হয় যখন ড্রাফটগুলোকে নেভিগেশনের বাইরে টিকে থাকতে হবে বা এডিট স্ক্রিনের বাইরেও দেখা দরকার হবে। একটি সাধারণ প্যাটার্ন হলো draftsById[customerId], যাতে প্রতিটি রেকর্ডের জন্য আলাদা ড্রাফট থাকে। এটি তখনও সাহায্য করে যখন ব্যবহারকারী একাধিক এডিট স্ক্রিন খুলতে পারে।
ড্রাফট বাগ সাধারণত কয়েকটি পূর্বানুমেয় ভুল থেকে হয়: রেকর্ড লোড হওয়ার আগে ড্রাফট তৈরি করা, রিফেচে ডার্টি ড্রাফট ওভাররাইট হওয়া, Cancel এ এরর ক্লিয়ার করা ভুলে যাওয়া, বা একটি একক শেয়ার্ড কী ব্যবহার করে ড্রাফটগুলোকে একে অপরের ওপর ওভাররাইট করা। যদি আপনি স্পষ্ট রুল সেট করেন (কখন তৈরি করা হবে, কখন ওভাররাইট করা হবে, কখন ডিসকার্ড করা হবে, পারসিস্ট করা হবে, এবং সেভের পর কবে রিপ্লেস করবে), অধিকাংশ সমস্যা দূর হয়ে যাবে।
আপনি AppMaster (appmaster.io) দিয়ে অ্যাপ বিল্ড করলেও “ড্রাফট বনাম সেভড রেকর্ড” বিভাজন একই থাকে: ড্রাফট ক্লায়েন্টে রাখুন, এবং ব্যাকএন্ডকে সোর্স অফ ট্রুথ হিসেবে বিবেচনা করুন কেবল সফল Save-এ।
Example 3: multi-tab editing without state collisions
মাল্টি-ট্যাব এডিটিংই সেই জায়গা যেখানে অ্যাডমিন প্যানেল প্রায়ই ভেঙে পড়ে। ইউজার Customer A খুলে, তারপর Customer B খুলে, ফিরে গেলে প্রত্যেক ট্যাব যেন নিজের আনসেভড পরিবর্তন মনে রাখে—এইটাই প্রত্যাশা।
ফিক্স হলো প্রতিটি ট্যাবকে তার নিজস্ব স্টেট বান্ডেল হিসেবে মডেল করা, একটি শেয়ার্ড ড্রাফট হিসেবে নয়। প্রতিটি ট্যাবের অন্তত একটি ইউনিক কী (সাধারণত রেকর্ড ID ভিত্তিক), ড্রাফট ডাটা, স্ট্যাটাস (clean, dirty, saving), এবং ফিল্ড এরর থাকা উচিত।
যদি ট্যাবগুলো একই স্ক্রিনের মধ্যে থাকে, লোকাল অ্যাপ্রোচ ভাল কাজ করে। ট্যাব লিস্ট এবং ড্রাফটগুলো সেই পেজ কম্পোনেন্টই মালিকানাধীন রাখুক যা ট্যাব রেন্ডার করে। প্রতিটি এডিটর প্যানেল কেবল তার নিজের বান্ডেল পড়বে ও লিখবে। যখন একটি ট্যাব বন্ধ হয়, সেই বান্ডেল মুছে ফেলুন—শেষ কথা। এটি আইসোলেটেড ও বোঝা সহজ রাখে।
স্টেট যেখানে-ই থাকুক, আকৃতি সমানরকম:
- ট্যাব অবজেক্টের তালিকা (প্রতিটি
customerId,draft,status, এবংerrorsসহ) - একটি
activeTabKey openTab(id),updateDraft(key, patch),saveTab(key), এবংcloseTab(key)মতো অ্যাকশন
যখন ট্যাবগুলোকে নেভিগেশনেও টিকে থাকতে হবে (Orders এ যান এবং ফিরে আসা), বা একাধিক স্ক্রিন ট্যাব খুলে ফোকাস করবে, তখন Pinia-র ছোট “tab manager” স্টোর ভাল পছন্দ।
প্রধান কোলাইজন এড়ানোর উপায় হলো একটি গ্লোবাল ভেরিয়েবল currentDraft এড়ানো। সেটি দ্বিতীয় ট্যাব খুললেই কাজ করা বন্ধ করে দেয়—এডিটগুলো একে অপরকে ওভাররাইট করে, ভ্যালিডেশন এরর ভুল জায়গায় দেখায়, এবং সেভ ভুল রেকর্ড আপডেট করে। প্রতিটি ওপেন ট্যাবে আলাদা বান্ডেল রাখলে কোলাইজন ডিজাইন অনুযায়ীই প্রায় অদৃশ্য হয়ে যায়।
Common mistakes that cause bugs and messy code
অধিকাংশ অ্যাডমিন প্যানেল বাগ “Vue বাগ” নয়। এগুলো স্টেট বাগ: ডেটা ভুল জায়গায় থাকে, স্ক্রিনের দুই অংশ একমত নয়, বা পুরনো স্টেট চুপচাপ লেগে থাকে।
সর্বাধিক দেখা প্যাটার্নগুলো:
-
ডিফল্টভাবে সবকিছু Pinia-তে রাখা হলে মালিকানা অনির্দিষ্ট হয়। একটি গ্লোবাল স্টোর প্রথমে সাজানো লাগে, কিন্তু পরে প্রতিটি পেজ একই অবজেক্ট পড়ে ও লিখতে শুরু করে এবং ক্লিনআপ আন্দাজে করা হয়।
-
স্পষ্ট চুক্তি ছাড়া
provide/injectব্যবহার করলে লুকানো ডিপেন্ডেন্সি তৈরি হয়। যদি একটি চাইল্ডfiltersইনজেক্ট করে কিন্তু কে সেটা প্রোভাইড করে এবং কোন অ্যাকশনগুলো পরিবর্তন করতে পারে সে সম্পর্কে স্পষ্ট না থাকে, অন্য একটি চাইল্ড যখন একই অবজেক্ট মিউটেট করবে তখন অবাক করা আপডেট দেখা যাবে। -
একই স্টোরে সার্ভার স্টেট ও UI স্টেট মিশিয়ে ফেলা দুর্ঘটনাজনক ওভাররাইট হওয়ার কারণ। ফেচ করা রেকর্ডগুলো অন্যান্য UI টগলগুলো থেকে আলাদা আচরণ করে। একসঙ্গে থাকলে রিফেচিং UI কে স্টাম্প করতে পারে, বা UI পরিবর্তন ক্যাশড ডাটাকে মিউটেট করে দিতে পারে।
-
লাইফসাইকেল ক্লিনআপ এড়ালে স্টেট লিক হয়। একটি ভিউ থেকে ফিল্টার অন্য ভিউতে ঝাপসা প্রভাব ফেলতে পারে, এবং ড্রাফটগুলো পেজ ছেড়ে যাওয়ার পরও থাকবে। পরেরবার কেউ অন্য রেকর্ড খুললে তারা পুরানো সিলেকশন দেখে অ্যাপটি ভাঙা মনে করবে।
-
ড্রাফট কী ভুলভাবে কীড করলে বিশ্বাস হারায়। যদি ড্রাফট
draft:editUserএকক কী-তে রাখা হয়, User A এবং পরে User B এডিট করলে একই ড্রাফট ওভাররাইট হবে।
একটি সহজ নিয়ম বেশিরভাগ সমস্যা প্রতিরোধ করে: স্টেটকে যতটা সম্ভব তার ব্যবহারের কাছে রাখুন, এবং কেবল তখনই লিফট করুন যখন দুই স্বাধীন অংশ সত্যিই শেয়ার করতে চায়। যখন আপনি শেয়ার করেন, মালিকানা (কে পরিবর্তন করতে পারে) ও পরিচয় (কিভাবে কীড করা হবে) স্পষ্টভাবে নির্ধারণ করুন।
A quick checklist before you pick local, provide/inject, or Pinia
সবচেয়ে দরকারী প্রশ্ন হলো: কে এই স্টেটের মালিক? যদি আপনি এক বাক্যে বলতে না পারেন, স্টেট সম্ভবত অতিরিক্ত ভার বহন করছে এবং ভাগ করা উচিত।
এই চেকগুলো দ্রুত ফিল্টার হিসেবে ব্যবহার করুন:
- আপনি কি মালিককে নাম করতে পারেন (একটি কম্পোনেন্ট, একটি পেজ, বা পুরো অ্যাপ)?
- এটা রুট পরিবর্তন বা রিলোডে টিকে থাকতে হবে? যদি হ্যাঁ, পারসিস্টেন্স পরিকল্পনা করুন বদলে ভরসা না করে।
- দুইটি রেকর্ড একসঙ্গে এডিট হবে কি? যদি হ্যাঁ, রেকর্ড ID দিয়ে স্টেট কী করুন।
- স্টেট কি শুধুমাত্র একটি পেজ শেলের অধীনে ব্যবহৃত হয়? যদি হ্যাঁ,
provide/injectপ্রায়ই মানায়। - আপনি কি পরিবর্তনগুলো ইনস্পেক্ট করে বুঝতে চান যে কে কি বদলিয়েছে? যদি হ্যাঁ, Pinia প্রায়ই সেই অংশের জন্য পরিষ্কার জায়গা।
টুল ম্যাচিং কেবল কথ্যভাবে:
যদি স্টেট এক কম্পোনেন্টের ভিতরে জন্ম নিয়ে সেখানেই মরে (যেমন একটা dropdown open/closed ফ্ল্যাগ), লোকালেই রাখুন। যদি একই স্ক্রিনের কয়েকটি কম্পোনেন্ট শেয়ার্ড কনটেক্সট চায় (filter bar + table + summary), provide/inject শেয়ার করে কিন্তু গ্লোবাল না করে। যদি স্টেট রুট জুড়ে শেয়ার করা লাগে, নেভিগেশন টিকে থাকতে হবে, বা পূর্বানুমেয় ডিবাগযোগ্য আপডেট দরকার, তাহলে Pinia নিন এবং ড্রাফটগুলোর ক্ষেত্রে রেকর্ড ID দিয়ে কী করুন।
আপনি যদি Vue 3 অ্যাডমিন UI বানান (AppMaster সহ), এই চেকলিস্ট আপনাকে খুব তাড়াতাড়ি সবকিছু স্টোরে ফেলতে বাধ্য করবে না।
Next steps: evolving state without creating a mess
অ্যাডমিন প্যানেলে স্টেট ম্যানেজমেন্ট উন্নত করার সবচেয়ে নিরাপদ উপায় হলো ছোট, নীরস ধাপে বাড়ানো। যা এক পেজের ভিতরে থাকে তা প্রথমে লোকাল রাখুন। যখন বাস্তবে পুনঃব্যবহার দেখা যায় (লজিক কপি হওয়া, তৃতীয় কোনো কম্পোনেন্টও একই স্টেট দরকার), তখন এক লেভেল উপরে উঠান। তারপর কেবল তখনই শেয়ার্ড স্টোর বিবেচনা করুন।
সাধারণ পথ যা বেশিরভাগ টিমে কাজ করে:
- প্রথমে পেজ-ওনলি স্টেট লোকাল রাখুন (ফিল্টার, সর্ট, পেজিনেশন, খোলা/বন্ধ প্যানেল)।
- একই পেজের বেশ কয়েকটি কম্পোনেন্ট শেয়ার করতে চাইলে
provide/injectব্যবহার করুন। - ক্রস-স্ক্রিন প্রয়োজনের জন্য এক সময়ে এক করে Pinia স্টোর যোগ করুন (draft manager, tab manager, current workspace)।
- রিসেট রুল লিখুন এবং সেগুলো ম্যানটেইন করুন (নেভিগেশন, লোগআউট, Clear filters, Discard changes এ কী হয়)।
রিসেট রুলগুলো ছোট মনে হলেও তারা বেশিরভাগ “এটা কেন বদলাল?” মুহূর্ত প্রতিরোধ করে। উদাহরণস্বরূপ সিদ্ধান্ত নিন: কেউ যখন অন্য রেকর্ড খুলে ফিরে আসে তখন ড্রাফটের কী হবে—রিস্টোর, সতর্ক করা, না রিসেট করা? তারপর সেই আচরণ ধারাবাহিক রাখুন।
যদি আপনি স্টোর যোগ করেন, সেটাকে ফিচার-শেপড রাখুন। একটি drafts স্টোর ড্রাফট তৈরি, রিস্টোর, এবং ক্লিয়ার করা হ্যান্ডেল করবে, কিন্তু সেটা টেবিল ফিল্টার বা UI লেআউট ফ্ল্যাগও নিজের দায়িত্ব না নিক।
যদি দ্রুত প্রোটোটাইপ করতে চান, AppMaster (appmaster.io) একটি Vue3 ওয়েব অ্যাপ প্লাস ব্যাকএন্ড ও বিজনেস লজিক জেনারেট করতে পারে, এবং আপনি যেখানে কাস্টম স্টেট হ্যান্ডলিং দরকার সেখানে জেনারেটেড কোড পরিমার্জন করতে পারবেন। একটি প্রায়োগিক পরবর্তী ধাপ হলো এক স্ক্রিন end-to-end বানানো (উদাহরণ: একটি এডিট ফর্ম ড্রাফট রিকভারি নিয়ে) এবং দেখা কোন অংশগুলো আসলেই Pinia লাগে এবং কীগুলি লোকালেই থাকতে পারে।
প্রশ্নোত্তর
লোকাল স্টেট ব্যবহার করুন যখন ডেটা শুধু একটিমাত্র কম্পোনেন্টকে প্রভাবিত করে এবং সেই কম্পোনেন্টটি আনমাউন্ট হলে ডেটা রিসেট হয়ে যেতে পারে। সাধারণ উদাহরণগুলো: ডায়ালগ খোলা/বন্ধ, এক টেবিলের নির্বাচিত সারি, এবং এমন একটি ফর্ম সেকশন যা অন্য কোথাও পুনরায় ব্যবহার হয় না।
provide/inject ব্যবহার করুন যখন একই পেজে বেশ কয়েকটি কম্পোনেন্টকে একটি জিওয়ান সোর্স অফ ট্রুথ লাগবে এবং prop drilling ঝামেলা বাড়াচ্ছে। দেওয়া ভ্যালুগুলো ছোট ও সতর্কভাবে রাখুন যাতে পেজটি পড়তে ও বুঝতে সহজ থাকে।
Pinia ব্যবহার করুন যখন স্টেট রুট পরিবর্তন জুড়ে শেয়ার করা হবে, নেভিগেশন টেকসই থাকতে হবে, অথবা একটি কেন্দ্রিক, ডিবাগযোগ্য জায়গায় পরিবর্তনগুলো দেখতে হবে। সাধারণ উদাহরণ: বর্তমান ওয়ার্কস্পেস, পারমিশন, ফিচার ফ্ল্যাগ, এবং ক্রস-স্ক্রিন ম্যানেজারস (ড্রাফট/ট্যাব ম্যানেজার)।
প্রথমে টাইপ নির্ধারণ করুন (UI, server, form, cross-screen), তারপর স্কোপ (একটি কম্পোনেন্ট, একটি পেজ, অনেক রুট), লাইফটাইম (আনমাউন্ট হলে রিসেট, নেভিগেশন টিকে থাকা, রিলোড টিকে থাকা) এবং কনকারেন্সি (একই সময়ে এক বা একাধিক এডিটর)। এগুলো থেকে টুল বেছে নিন।
যদি ইউজাররা ভিউ শেয়ার বা রিস্টোর করতে চান, তাহলে ফিল্টার ও পেজিনেশন query params-এ রাখুন যাতে রিলোডে টিকে থাকে এবং কপি করা যায়। যদি মূল চাহিদা হলো “রুট পরিবর্তন করে ফিরে এলেই তালিকা ঠিক আগের মতো থাকুক”, তাহলে Pinia-তে তালিকা মডেল রাখুন; নাহলে পেজ-স্কোপেই রাখুন।
সর্বশেষ সেভ করা রেকর্ড, ব্যবহারকারীর স্টেজড এডিট, এবং UI-নির্ভর স্টেট (যেমন ভ্যালিডেশন এরর) আলাদা রাখুন এবং শুধু Save এ ব্যাকএন্ডে লেখুন। একটি স্পষ্ট dirty রুল রাখুন এবং নেভিগেশনে কী হবে (ওয়ার্ন, অটো-সেভ, বা রিকভারেবল ড্রাফট রাখা) ঠিক করুন।
প্রতিটি ওপেন এডিটরের জন্য আলাদা স্টেট বানান, যা রেকর্ড ID বা ট্যাব কী দিয়ে কীড করা। একক গ্লোবাল currentDraft ব্যবহার করবেন না—সেটি দ্বিতীয় ট্যাব খুললেই কনফ্লিক্ট তৈরি করে।
পেজ-সোয়ান্ড provide/inject ঠিক আছে যদি সম্পূর্ণ এডিট ফ্লো এক রুটেই সীমাবদ্ধ থাকে। যদি ড্রাফটগুলো রুট পরিবর্তনের পর টিকে থাকা বা এডিট স্ক্রিনের বাইরেও দরকার হয়, তাহলে Pinia-তে draftsById[recordId] মত কাঠামো সাধারণত সহজ ও পূর্বানুমেয় হয়।
যা কম্পিউট করে পাওয়া যায় তা স্টোর করবেন না। ব্যাজ, সংক্ষিপ্ত সারাংশ, এবং “can save” টাইপ লজিক computed থেকে নিন যেন এগুলো সিঙ্ক থেকে বিচ্যুত না হয়।
ডিফল্টভাবে সবকিছু Pinia-তে রাখা, সার্ভার রেসপন্সের সঙ্গে UI টগল মিশিয়ে ফেলা, এবং নেভিগেশনে ক্লিনআপ না করা—এগুলো সবচেয়ে সাধারণ ত্রুটি। এছাড়া একটাই শেয়ার্ড ড্রাফট কী ব্যবহার করা যেখানে ভিন্ন রেকর্ডগুলো একে অপরকে ওভাররাইট করে ফেলে—এইটাও বড় সমস্যা।


