React স্টেট ম্যানেজমেন্ট সহজ করা: সার্ভার স্টেট আলাদা রাখুন ক্লায়েন্ট স্টেট থেকে, কয়েকটি নিয়ম মেনে চলুন, এবং জটিলতা বাড়ার আগের লক্ষণগুলো চিনে নিন।

useEffect যোগ করেন, এবং এখন আপনার কাছে সত্যের দুইটি উৎস আছে। যদি জেনারেটর বা টিম মাঝপথে দিক বদলায় (এখানে লোকাল স্টেট, সেখানে গ্লোবাল স্টোর), কোডবেসটি একটির উপর নির্মাণ করার বদলে প্যাটার্ন সংগ্রহ করে নেয়।\n\nলক্ষ্যটি হলো সাধারণ: কম স্টেট টাইপ, এবং খোঁজার জন্য কম জায়গা। যখন সার্ভার ডেটার জন্য একটি স্পষ্ট বাড়ি এবং কেবল UI-র জন্য একটি স্পষ্ট বাড়ি থাকে, বাগগুলো ছোট হয়ে যায় এবং পরিবর্তনগুলো ঝুঁকিপূর্ণ মনে হয় না।\n\n“নিরস রাখুন” মানে আপনি কয়েকটি নিয়ম মেনে চলেন:\n\n- পরিষ্কার কারণ ছাড়া সার্ভার ডেটাকে ক্লায়েন্ট স্টেটে মিরর করবেন না।\n- ভবিষ্যতে দরকার হতে পারে বলে লোকাল UI স্টেটকে গ্লোবাল স্টোরে প্রচার করবেন না।\n- ডেরাইভড ভ্যালুকে আলাদা স্টেট বানিয়ে ফেলবেন না যদি না তা গণনা করা সত্যিই খরচসাপেক্ষ।\n\nএকটি স্পষ্ট উদাহরণ: যদি একটি ইউজার তালিকা ব্যাকএন্ড থেকে আসে, এটাকে সার্ভার স্টেট হিসেবে বিবেচনা করুন এবং যেখানে দরকার ওখানেই ফেচ করুন। যদি selectedUserId কেবল একটি ডিটেইল প্যানেল চালাতে থাকে, সেটাকে সেই প্যানেলের কাছাকাছি ছোট UI স্টেট হিসেবে রাখুন। এই দুইটি মিশানো হল ক্লকসিxty শুরু হওয়ার পথ।\n\n## সার্ভার স্টেট বনাম ক্লায়েন্ট স্টেট সরল ভাষায়\n\nঅধিকাংশ React স্টেট সমস্যা একটি মিক্স-আপ থেকে শুরু হয়: সার্ভার ডেটাকে UI স্টেটের মত আচরণ করা। শুরুতেই এগুলো আলাদা করুন, তখন স্টেট ম্যানেজমেন্ট শান্ত থাকে — এমনকি অ্যাপ বাড়লেও।\n\nসার্ভার স্টেট ব্যাকএন্ডের: users, orders, tasks, permissions, দাম, feature flags। এটা আপনার অ্যাপ কিছু না করেই পরিবর্তিত হতে পারে (অন্য ট্যাবে আপডেট, একজন অ্যাডমিন এডিট, একটি ব্যাকজব চলে, ডেটা মেয়াদোত্তীর্ণ)। কারণ এটি শেয়ার্ড ও পরিবর্তনশীল, তাই ফেচিং, ক্যাশিং, রিফেচিং, এবং এরর হ্যান্ডলিং দরকার।\n\nক্লায়েন্ট স্টেট হলো যা কেবল আপনার UI-কে এখনই গুরুত্বপূর্ণ: কোন মডাল খোলা, কোন ট্যাব সিলেক্টেড, ফিল্টার টগল, সোর্ট অর্ডার, কোল্যাপ্স করা sidebar, একটি ড্রাফট সার্চ কোয়েরি। আপনি যদি ট্যাব বন্ধ করেন, এটাকে হারানো ঠিক আছে।\n\nএকটি দ্রুত টেস্ট: “আমি কি পেজ রিফ্রেশ করে এটা সার্ভার থেকে পুনর্নির্মাণ করতে পারব?”\n\n- যদি হ্যাঁ, সম্ভবত সার্ভার স্টেট।\n- যদি না, সম্ভবত ক্লায়েন্ট স্টেট।\n\nআর আছে ডেরাইভড স্টেট, যা আপনাকে অতিরিক্ত স্টেট বানানো থেকে রক্ষা করে। এটি এমন একটি মান যা আপনি অন্য মান থেকে গণনা করতে পারেন, তাই আপনি তা স্টোর করেন না। Filtered lists, totals, isFormValid, এবং “show empty state” সাধারণত এখানে থাকে।\n\nউদাহরণ: আপনি একটি প্রজেক্ট তালিকা ফেচ করেন (server state)। নির্বাচিত ফিল্টার এবং “New project” ডায়ালগ খোলা থাকা client state। ফিল্টার করার পর দৃশ্যমান তালিকা derived state। যদি আপনি দৃশ্যমান তালিকাকে আলাদাভাবে স্টোর করেন, তা সিঙ্ক থেকে বিচ্যুত হবে এবং আপনি “এটা কেন stale?” বাগগুলোর পিছনে ছুটবেন।\n\nএই বিভাজন সাহায্য করে যখন Koder.ai-এর মত টুল স্ক্রিন দ্রুত জেনারেট করে: ব্যাকএন্ড ডেটাকে একটি ফেচিং লেয়ারে রাখুন, UI পছন্দগুলোকে কম্পোনেন্টের কাছে রাখুন, এবং গণিত করা মানগুলি স্টোর করা এড়ান।\n\n## বেশিরভাগ সমস্যা রোধ করার জন্য একটি সাধারণ নিয়মাবলী\n\nস্টেট তখনই কষ্টদায়ক হয় যখন একটি ডেটার দুইটি মালিক থাকে। দ্রুততম উপায় সহজ রাখার হলো কে কী মালিক তা ঠিক করে তা মানা।\n\n- API ডেটাকে এমন কিছু হিসেবে বিবেচনা করুন যা আপনি ফেচ এবং ক্যাশ করেন, না যে আপনি কম্পোনেন্ট স্টেটে ম্যানুয়ালি রক্ষা করবেন।\n- ফেচ করা ডেটাকে লোকাল স্টেটে “জাস্ট ইন কেস” কপি করবেন না। কপি করলে drift তৈরি হয়।\n- ক্লায়েন্ট স্টেটকে যেখানে ব্যবহার হয় ওখানেই রাখুন। কেবল তখন উপরে তুলুন যখন সত্যিকারভাবে ট্রি’র আলাদা অংশগুলো সেটি প্রয়োজন।\n- সম্পূর্ণ অবজেক্ট নয়, ID এবং ছোট ফ্ল্যাগ স্টোর করুন। রেন্ডার করার সময় ক্যাশ থেকে অবজেক্ট পুনরায় বের করুন।\n\nউদাহরণ: আপনি একটি ইউজার তালিকা ফেচ করেন এবং একজন নির্বাচিত হলে ডিটেইল দেখান। সাধারণ ভুল হলো পুরো selected user অবজেক্ট স্টেট হিসেবে রাখা। selectedUserId রাখুন। তালিকা সার্ভার ক্যাশে থাকুক। ডিটেইল ভিউ ID দিয়ে ইউজার খুঁজে দেখবে, তাই রিফেচ করলে UI অতিরিক্ত সিঙ্কিং কোড ছাড়াই আপডেট হবে।\n\nজেনারেটেড React অ্যাপগুলোতেও সহজভাবে “সহায়ক” জেনারেটেড স্টেট দেখা যায় যা সার্ভার ডেটাকে ডুপ্লিকেট করে। যখন আপনি এমন কোড দেখেন যে করে: fetch -> setState -> edit -> refetch, তখন থামুন। এটি প্রায়ই একটি দ্বিতীয় ব্রাউজার-ডেটাবেস বানানোর লক্ষণ।\n\n## সার্ভার স্টেটকে অতি চিন্তা না করে কিভাবে হ্যান্ডেল করবেন\n\nসার্ভার স্টেট যেকোনো যে ব্যাকএন্ডে থাকে: লিস্ট, ডিটেইল পেজ, সার্চ রেজাল্ট, পারমিশন, কাউন্ট। নিরস পথে একটা টুল বেছে নিন এবং সেটাই ব্যবহার করুন। অনেক React অ্যাপের জন্য TanStack Query যথেষ্ট।\n\nলক্ষ্যটি সোজা: কম্পোনেন্টগুলো ডেটা চায়, লোডিং এবং এরর দেখায়, এবং কতগুলো ফেচ আলাদাভাবে ঘটছে তা নিয়ে ব্যাথা করে না। জেনারেটেড অ্যাপগুলিতে এটা গুরুত্বপূর্ণ কারণ ছোট অসামঞ্জস্যগুলো দ্রুত গুণিত হয়ে নতুন স্ক্রিন যোগ হলে বড় হয়ে যায়।\n\nকুয়েরি কীগুলোকে একটি নামকরণ পদ্ধতি হিসেবে বিবেচনা করুন, পরে ভাবার মত কিছু নয়। সেগুলো কনসিস্টেন্ট রাখুন: স্থিতিশীল অ্যারে কীগুলো, শুধুই সেই ইনপুটগুলো অন্তর্ভুক্ত করুন যা ফলাফল বদলে দেয় (filters, page, sort), এবং অনেক এক-অফের তুলনায় কয়েকটি পূর্বানুমেয় শেপ পছন্দ করুন। অনেক দল ছোট হেলপারগুলোতে key-building রাখে যাতে প্রতিটি স্ক্রিন একই নিয়ম ব্যবহার করে।\n\nলিখনের জন্য মিউটেশন ব্যবহার করুন এবং স্পষ্ট সাকসেস হ্যান্ডলিং রাখুন। একটি মিউটেশনকে দুইটি প্রশ্নের উত্তর দেওয়া উচিত: কী পরিবর্তিত হল, এবং UI পরবর্তীতে কী করবে?\n\nউদাহরণ: আপনি একটি নতুন টাস্ক তৈরি করেন। সফল হলে, বা তো তালিকা query invalidate করুন (একবার রিলোড হবে) বা ক্যাশে টার্গেটেড আপডেট করুন (নতুন টাস্ক ক্যাশড লিস্টে যোগ করুন)। প্রতি ফিচারের জন্য একটি পদ্ধতি বেছে নিন এবং কনসিস্টেন্ট থাকুন।\n\nযদি আপনাকে একাধিক স্থানে রিফেচ কল যোগ করতে ইচ্ছে করে “নিশ্চিত থাকতে,” একটি একক নিরস পদক্ষেপ বেছে নিন:\n\n- যেই নির্দিষ্ট কোরি কী stale হয়েছে তা invalidate করুন।\n- পরিবর্তিত নির্দিষ্ট কোরি জন্য ক্যাশ আপডেট করুন।\n- এমন একটি স্ক্রিনে নেভিগেট করুন যা ইতিমধ্যেই সঠিক কোরি ফেচ করে।\n\n## ক্লায়েন্ট স্টেট প্যাটার্নগুলো যা ছোট থাকে\n\nক্লায়েন্ট স্টেট হচ্ছে ব্রাউজারের মালিক: sidebar open flag, নির্বাচিত রো, filter পাঠ্য, একটি ড্রাফট যা আপনি সেভ করেননি। যেখানে ব্যবহার হয় ওখানেই রাখলে এটি সাধারণত ম্যানেজেবল থাকে।\n\nছোট থেকে শুরু করুন: নিকটতম কম্পোনেন্টে useState। যখন আপনি স্ক্রিন জেনারেট করেন (উদাহরণস্বরূপ Koder.ai দিয়ে), সবকিছুই “ভবিষ্যতে দরকার হতে পারে” বলে গ্লোবাল স্টোরে ঠেলে দেওয়ার টেনে আটকানো কঠিন — এভাবে এমন একটি স্টোর তৈরি হয় যা কেউই বুঝে না।\n\n### একটি সহজ প্রোমোশন নিয়ম\n\nশেয়ারিং সমস্যার নাম না জানালে state ওপরে তুলবেন না।\n\n- ডিফল্টে UI-শুধু স্টেট লোকাল রাখুন।\n- সিবলিংস যখন দরকার হবে তখন সবচেয়ে কাছের কমন প্যারেন্টে লিফট করুন।\n- যখন একাধিক রুট বা দূরের কম্পোনেন্ট একই সময়ে একই মান চাই তখন একটি ছোট শেয়ার্ড স্টোর ব্যবহার করুন।\n\nউদাহরণ: একটি টেবিল যার ডিটেইল প্যানেল আছে — selectedRowId টেবিল কম্পোনেন্টে রাখুন। যদি পেজের অন্য অংশের একটি টুলবারও সেটি চায়, পেজ কম্পোনেন্টে লিফট করুন। যদি আলাদা একটি রুট (যেমন bulk edit) সেটিও চায়, তখন ছোট স্টোর যুক্ত করা যৌক্তিক।\n\n### পাঠযোগ্য থাকার জন্য স্টোর শেপ\n\nযদি আপনি স্টোর ব্যবহার করেন (Zustand বা অনুরূপ), এটাকে এক কাজের উপর ফোকাসড রাখুন। স্টোরে "কি" রাখুন (selected IDs, filters), না যে "ফলাফল" (sorted lists) যেগুলো আপনি ডেরাইভ করতে পারেন।\n\nস্টোর বড় হয়ে উঠলে জিজ্ঞাসা করুন: এটা কি এখনো এক ফিচার? যদি সততার উত্তর “আংশিক,” হয়, এখনই বিভাজন করুন, পরবর্তী ফিচারটা এটাকে ভয়ানক করে দেওয়ার আগে।\n\n## ফর্ম, ড্রাফট, এবং সাময়িক UI স্টেট\n\nফর্ম বাগ সাধারণত তিনটি জিনিস মিশে যাওয়ায় আসে: ইউজার যা টাইপ করছে, সার্ভার কী সেভ করেছে, এবং UI কী দেখাচ্ছে।\n\nনিরস স্টেট ম্যানেজমেন্টের জন্য, ফর্মকে সাবমিট না হওয়া পর্যন্ত ক্লায়েন্ট স্টেট হিসেবে বিবেচনা করুন। সার্ভার ডেটা হচ্ছে শেষ সেভ করা ভার্সন। ফর্ম একটি ড্রাফট। সার্ভার অবজেক্টকে ইন-প্লেস এডিট করবেন না। মানগুলো ড্রাফটে কপি করুন, ইউজারকে স্বাধীনভাবে পরিবর্তন করতে দিন, তারপর সেভ করে সফল হলে রিফetch বা ক্যাশ আপডেট করুন।\n\nপ্রস্তুতিই নির্ধারণ করুন কোনটি ইউজার নেভিগেট করলে টিকে থাকবে। সেই এক সিদ্ধান্ত অনেক অবাঞ্ছিত বাগ রুখে দেয়। উদাহরণস্বরূপ, ইনলাইন এডিট মোড এবং খোলা ড্রপডাউন সাধারণত রিসেট হওয়া উচিত, আর একটি দীর্ঘ উইজার্ড ড্রাফট বা না-পাঠানো মেসেজ ড্রাফট হয়তো টিকে থাকা উচিত। রিলোড পার্সিস্ট কেবল তখনি করুন যখন ব্যবহারকারীরা স্পষ্টভাবে আশা করে (যেমন চেকআউট ফর্ম)।\n\nভ্যালিডেশন নিয়মগুলো এক জায়গায় রাখুন। যদি আপনি নিয়মগুলো ইনপুট, সাবমিট হ্যান্ডলার, এবং হেল্পারদের মধ্যে ছড়িয়ে ফেলেন, আপনি মিল না খাওয়া এরর পাবেন। একটি স্কিমা (বা একটি validate() ফাংশন) পছন্দ করুন, এবং UI ঠিক করবে কবে এরর দেখাবে (on change, on blur, অথবা on submit)।\n\nউদাহরণ: আপনি Koder.ai দিয়ে একটি Edit Profile স্ক্রিন জেনারেট করেন। সেভ করা প্রোফাইলটি সার্ভার স্টেট হিসেবে লোড করুন। ফর্ম ফিল্ডগুলো জন্য ড্রাফট স্টেট তৈরি করুন। "unsaved changes" দেখাতে ড্রাফট বনাম সেভড তুলনা করুন। যদি ইউজার বাতিল করে, ড্রাফট বাদ দিন এবং সার্ভার ভার্সন দেখান। যদি তারা সেভ করে, ড্রাফট সাবমিট করুন, তারপর সার্ভার রেসপন্স দিয়ে সেভড ভার্সন প্রতিস্থাপন করুন।\n\n## ধাপে ধাপে: একটি ঝামেলাপূর্ণ স্টেট সেটআপকে সাধারণ করা\n\nএকটি জেনারেটেড React অ্যাপ বাড়ার সঙ্গে সাধারণত একই ডেটা তিন জায়গায় শেষ হয়: কম্পোনেন্ট স্টেট, একটি গ্লোবাল স্টোর, এবং একটি ক্যাশ। ঠিক করা প্রায়ই নতুন কোনো লাইব্রেরি নয়—প্রত্যেকটা স্টেটের জন্য একটি বাড়ি বেছে নেওয়া।\n\nএকটি ক্লিনআপ ফ্লো যা বেশিরভাগ অ্যাপে কাজ করে:\n\n1. আপনার স্টেট ইনভেন্টরি তৈরি করুন। আপনার কাছে কি আছে (লিস্ট, নির্বাচিত আইটেম, ফিল্টার, মডাল, ড্রাফট) তালিকাভুক্ত করে প্রতিটি কে server না client হিসেবে লেবেল করুন।\n2. ডুপ্লিকেট মুছুন। যদি আপনি filteredUsers users + filter থেকে গণনা করতে পারেন, তাহলে তা মুছুন। selectedUser কপি করার বদলে selectedUserId রাখুন।\n3. ফেচিংকে এক লেয়ারে রাখুন। একটি server-state অ্যাপ্রোচ ব্যবহার করুন যাতে ক্যাশিং, রিফেচিং, এবং invalidation-এর জন্য এক নিয়ম থাকে।\n4. ছোট ক্লায়েন্ট স্টোর যোগ করুন কেবল যেখানে সেটি সত্যিই দরকার (ক্রস-পেজ UI প্রয়োজনীয়তা যেমন একটি উইজার্ড ড্রাফট)।\n5. নামকরণ নিয়ম লক করুন যাতে সেই গন্ডগোল ফিরে না আসে।\n\nউদাহরণ: একটি Koder.ai জেনারেটেড CRUD অ্যাপ প্রায়শই useEffect ফেচ প্লাস গ্লোবাল স্টোর কপি দিয়ে শুরু করে। সার্ভার স্টেট কেন্দ্রীভূত করার পরে তালিকাটি একটি কোরি থেকে আসে, এবং "refresh" মানে invalidate করা হয়ে যায় ম্যানুয়াল সিঙ্কিংয়ের বদলে।\n\nনামকরণের জন্য, কনসিস্টেন্ট এবং সাধারণ রাখুন:\n\n- Queries: users.list, users.detail(id)\n- Client state: ui.isCreateModalOpen, filters.userSearch\n- Actions: openCreateModal(), setUserSearch(value)\n- Server writes: users.create, users.update, users.delete\n\nলক্ষ্যটা প্রতিটি জিনিসের জন্য এক উৎস রাখা, এবং সার্ভার স্টেট ও ক্লায়েন্ট স্টেটের মধ্যে স্পষ্ট সীমারেখা।\n\n## আপনার স্টেট কমপ্লেক্সিটি বিস্ফোরিত হওয়ার আগের সতর্ক সংকেত\n\nস্টেট সমস্যা ছোট থেকে শুরু হয়, তারপর একদিন আপনি একটি ফিল্ড পরিবর্তন করলে UI-এর তিনটি অংশই বাস্তব মান নিয়ে অসামঞ্জস্য দেখায়।\n\nসাবধানের সবচেয়ে পরিষ্কার চিহ্ন হলো ডুপ্লিকেটেড ডেটা: একই ইউজার বা কার্ট কম্পোনেন্টে, গ্লোবাল স্টোরে, এবং request ক্যাশে থাকে। প্রতিটি কপি বিভিন্ন সময়ে আপডেট হয়, এবং আপনি তাদের সমান রাখতে আরও কোড যোগ করেন।\n\nআরেকটি চিহ্ন হলো সিঙ্ক কোড: effect যা স্টেটগুলো পিছনে-পেছনে ঠেলে দেয়। "when query data changes, update the store" এবং "when the store changes, refetch"-এর মত প্যাটার্নগুলো কাজ করে যতক্ষণ না কোনো এজ কেস stale ভ্যালু বা লুপ ট্রিগার করে।\n\nকয়েকটি দ্রুত রেড ফ্ল্যাগ:\n\n- আপনি এক বাক্যে বলতে পারেন না "এই ভ্যালুটি কোথা থেকে আসে" এবং "কে এর মালিক"।\n- আপনার স্টোর বা reducer API ক্লায়েন্ট ইম্পোর্ট করে বা HTTP স্ট্যাটাস কোড জানে।\n- প্রতিটি নতুন স্ক্রিন shared ফ্ল্যাগ যোগ করে needsRefresh, didInit, isSaving যা কেউ মুছে না।\n- আপনি প্রধানত effect লিখছেন একটি স্টেটকে অন্যটিতে মিরর করার জন্য।\n- বাগ ফিক্স করতে একই ফিল্ড একাধিক লেয়ারে আপডেট করতে হয়।\n\nউদাহরণ: আপনি Koder.ai দিয়ে একটি ড্যাশবোর্ড জেনারেট করেন এবং একটি Edit Profile মডাল যোগ করেন। যদি প্রোফাইল ডেটা একটি query ক্যাশে থাকে, গ্লোবাল স্টোরে কপি করা হয়, এবং লোকাল ফর্ম স্টেটে ডুপ্লিকেট করা হয়, তাহলে আপনার কাছে তিনটি সত্যের উৎস হবে। ব্যাকগ্রাউন্ড রিফেচিং বা optimistic updates যোগ করার প্রথমবার mismatches দেখা দেয়।\n\nএই চিহ্নগুলো দেখা দিলে, নির্বিবাদভাবে প্রতিটি ডেটার জন্য এক মালিক বেছে নেওয়া এবং মিররগুলো মুছে ফেলা নিরস পদক্ষেপ।\n\n## সাধারণ ফাঁদ এবং কিভাবে এড়াবেন\n\n"জাস্ট ইন কেস" রেখে রাখাটা স্টেটকে দ্রুত কষ্টদায়ক করে, বিশেষ করে জেনারেটেড অ্যাপে।\n\nAPI রেসপন্সগুলোকে গ্লোবাল স্টোরে কপি করা একটি সাধারণ ফাঁদ। যদি ডেটা সার্ভার থেকে আসে (লিস্ট, ডিটেইল, ইউজার প্রোফাইল), তা ডিফল্টভাবে ক্লায়েন্ট স্টোরে কপি করবেন না। সার্ভার ডেটার জন্য একটি বাড়ি বেছে নিন (সাধারণত query cache)। ক্লায়েন্ট স্টোর সার্ভার না জানে এমন UI-শুধু মানগুলোর জন্য ব্যবহার করুন।\n\nডেরাইভড ভ্যালুগুলো স্টোর করা আরেক ফাঁদ। কাউন্ট, filtered lists, totals, canSubmit, isEmpty সাধারণত ইনপুট থেকে গণনা করা উচিত। পারফরম্যান্স যদি বাস্তবে সমস্যা হয়, পরে memoize করুন, কিন্তু শুরুতেই ফলাফল স্টোর করা থেকে বিরত থাকুন যা stale হতে পারে।\n\nসব কিছুতেই একটি মেগা-স্টোর (auth, modals, toasts, filters, drafts, onboarding flags) একটি ডাম্পিং গ্রাউন্ড হয়ে যায়। ফিচার বাউন্ডারি অনুযায়ী ভাগ করুন। যদি স্টেট কেবল একটি স্ক্রিন দিয়ে ব্যবহার হয়, লোকালেই রাখুন।\n\nContext স্থিতিশীল মান (theme, current user id, locale) জন্য ভালো। দ্রুত পরিবর্তনশীল মানগুলোর জন্য এটি বিস্তৃত re-render সৃষ্টি করতে পারে। Context কেবল wiring-এর জন্য রাখুন, এবং দ্রুত পরিবর্তনশীল UI মানগুলির জন্য component state (বা একটি ছোট স্টোর) ব্যবহার করুন।\n\nঅবশেষে, অসামঞ্জস্যপূর্ণ নামকরণ এড়ান। নিকট-ডুপ্লিকেট query keys এবং স্টোর ফিল্ড সূক্ষ্ম ডুপ্লিকেশন তৈরি করে। একটি সরল স্ট্যান্ডার্ড নিন এবং অনুসরণ করুন।\n\n## আর একটি স্টেট যোগ করার আগে দ্রুত চেকলিস্ট\n\nআপনি যখন "আর একটি স্টেট ভ্যারিয়েবল" যোগ করার ইচ্ছা অনুভব করেন, দ্রুত একটি মালিকানা চেক করুন।\n\nপ্রথমে, কি আপনি একটি জায়গা নির্দেশ করতে পারবেন যেখানে সার্ভার ফেচিং এবং ক্যাশিং ঘটে (একটি কুইরি টুল, একটি সেট কুইরি কীগ)? যদি একই ডেটা একাধিক কম্পোনেন্টে ফেচ হয় এবং পাশাপাশি স্টোরেও কপি করা হয়, আপনি ইতিমধ্যেই সুদ দিচ্ছেন।\n\nদ্বিতীয়, এই মানটি কি কেবল একটি স্ক্রিনের ভিতরেই দরকার? যদি তাই হয় (যেমন "is filter panel open"), এটি গ্লোবাল হওয়া উচিত নয়।\n\nতৃতীয়, আপনি কি একটি অবজেক্টের বদলে একটি ID রাখতে পারেন? selectedUserId রাখুন এবং ক্যাশ বা লিস্ট থেকে ইউজার পড়ুন।\n\nচতুর্থ, এটি কি ডেরাইভড? যদি আপনি বিদ্যমান স্টেট থেকে এটি গণনা করতে পারেন, স্টোর করবেন না।\n\nঅবশেষে, একটি এক-মিনিট ট্রেস টেস্ট করুন। যদি একটি দলীয় সদস্য এক মিনিটের মধ্যে বলতে না পারে "এই ভ্যালুটি কোথা থেকে আসে?" (prop, local state, server cache, URL, store), তবে নতুন স্টেট যোগ করার আগে মালিকানা ঠিক করুন।\n\n## উদাহরণ: একটি CRUD অ্যাপ যা বাড়লেও নিরস থাকে\n\nএকটি জেনারেটেড অ্যাডমিন অ্যাপ কল্পনা করুন (উদাহরণস্বরূপ, Koder.ai থেকে প্রম্পট করে উত্পন্ন) যার তিনটি স্ক্রিন আছে: কাস্টমার লিস্ট, কাস্টমার ডিটেইল পেজ, এবং একটি এডিট ফর্ম।\n\nস্টেট শান্ত থাকে যখন এর পরিষ্কার বাড়ি থাকে:\n\n- সার্ভার স্টেট: কাস্টমার লিস্ট, কাস্টমার রেকর্ড, সেভ/ডিলিট রিকোয়েস্ট।\n- ক্লায়েন্ট স্টেট: লিস্ট ফিল্টার, নির্বাচিত রো, এডিট ড্রাফট।\n\nলিস্ট এবং ডিটেইল পেজগুলো query ক্যাশ থেকে সার্ভার স্টেট পড়ে। আপনি সেভ করলে কাস্টমারগুলো গ্লোবাল স্টোরে আবার রাখেন না। আপনি মিউটেশন পাঠান, তারপর ক্যাশ রিফ্রেশ বা আপডেট করতে দেন।\n\nএডিট স্ক্রিনের জন্য, ফর্ম ড্রাফট লোকালেই রাখুন। ফেচ করা কাস্টমার থেকে ইনিশিয়ালাইজ করুন, কিন্তু ইউজার টাইপ করা শুরু করলে এটি আলাদা হিসেবে আচরণ করুক। এভাবে, ডিটেইল ভিউ নিরাপদে রিফ্রেশ করতে পারে बिना অর্ধেক-সেইভ করা পরিবর্তনগুলো ওভাররাইট করে।\n\n### একটি নিরস optimistic আপডেট\n\nOptimistic UI-তে দলগুলো প্রায়শই সবকিছু ডুপ্লিকেট করে। সাধারণত আপনাকে তা করতে হবে না।\n\nযখন ইউজার Save চাপবে, কেবল ক্যাশড কাস্টমার রেকর্ড এবং ম্যাচিং লিস্ট আইটেম আপডেট করুন, তারপর রিকোয়েস্ট ফেল করলে রোলব্যাক করুন। ফর্ম ড্রাফট যতক্ষণ সেভ সফল না হচ্ছে ততক্ষণ ধরে রাখুন। ফেল করলে একটি এরর দেখান এবং ইউজার যে retry করতে পারে সেই সুবিধা রাখুন।\n\n### যখন দ্বিতীয় একটি ফিচার একই স্টেট চায়\n\nধারণা করুন আপনি bulk edit যোগ করেছেন এবং সেটাও selected rows চায়। নতুন স্টোর তৈরি করার আগে জিজ্ঞাসা করুন: এই স্টেট কি নেভিগেশন ও রিফ্রেশ পার করবে?\n\n- যদি হ্যাঁ, URL-এ রাখুন (selected IDs, filters)।\n- যদি না, পেজ কম্পোনেন্টেই রাখুন।\n- যদি বহু দূরের কম্পোনেন্ট সত্যিই একই সময়ে এটাকে চায় (toolbar + table + footer), তাহলে কেবল সেই ক্লায়েন্ট স্টেটের জন্য একটি ছোট শেয়ার্ড স্টোর যোগ করুন।\n\n## পরবর্তী ধাপ: জেনারেটেড অ্যাপে আপনার নিয়মগুলো কনসিস্টেন্ট রাখুন\n\nজেনারেটেড স্ক্রিন দ্রুত বাড়তে পারে, এবং সেটাই ভালো—যদি প্রতিটি স্ক্রিন আলাদা স্টেট রুল না আনে। রিপোতে একটি সংক্ষিপ্ত টিম নোট লিখে রাখুন: কীকে সার্ভার স্টেট ধরা হবে, কীকে ক্লায়েন্ট স্টেট ধরা হবে, এবং কোন টুল কোনটির মালিক। এটাকে এত সংক্ষিপ্ত রাখুন যে মানুষ আসলে অনুসরণ করবে।\n\nএকটি ছোট পুল রিকোয়েস্ট অভ্যাস যোগ করুন: প্রতিটি নতুন স্টেটকে server না client লেবেল দিন। যদি এটি সার্ভার স্টেট হয়, জিজ্ঞাসা করুন "এটি কোথায় লোড হয়, কীভাবে ক্যাশ করা হয়, এবং কী এটাকে invalidate করে?" যদি এটি ক্লায়েন্ট স্টেট হয়, জিজ্ঞাসা করুন "কে এর মালিক, এবং কখন এটি রিসেট হবে?"\n\nআপনি যদি Koder.ai (Koder.ai) ব্যবহার করে থাকেন, Planning Mode আপনাকে নতুন স্ক্রিন জেনারেট করার আগে স্টেট বাউন্ডারি নিয়ে একমত হতে সাহায্য করতে পারে। একটি স্ন্যাপশট এবং রোলব্যাক আপনাকে নিরাপদ উপায় দেয় যখন একটি স্টেট পরিবর্তন গোলমেল করে, তখন সহজে ফিরে আসার অপশন রাখে।\n\nএকটি ফিচার বেছে নিন (যেমন edit profile), নিয়মগুলো end-to-end প্রয়োগ করুন, এবং সেটিকেই সবার কপি করার উদাহরণ হিসেবে রাখুন।প্রতিটি স্টেটকে লেবেল করুন: server, client (UI), অথবা derived।\n\n- Server: fetched data যেমন users, tasks, permissions.\n- Client: UI পছন্দসমূহ যেমন “modal open”, নির্বাচিত ট্যাব, filter পাঠ্য.\n- Derived: যে মানগুলো অন্য মান থেকে গণনা করা যায় (filtered list, totals, isValid).\n\nলেবেল করার পর, নিশ্চিত করুন প্রতিটি আইটেমের একটি সুস্পষ্ট মালিকানা আছে (query cache, স্থানীয় component state, URL, অথবা একটি ছোট স্টোর)।
দ্রুত টেস্ট: “আমি কি পেজ রিফ্রেশ করে এটাকে সার্ভার থেকে পুনরায় তৈরি করতে পারি?”\n\n- যদি হ্যাঁ, এটি সম্ভবত server state (fetch/cache/refetch)।\n- যদি না, এটি সম্ভবত client state (লোকাল UI স্টেট, সম্ভবত স্টোর)।\n\nউদাহরণ: একটি প্রজেক্ট লিস্ট server state; নির্বাচিত রো আইডি client state।
কারণ এতে দুইটি সত্যের উৎস তৈরি হয়।\n\nআপনি যদি users ফেচ করে সেটাকে useState বা গ্লোবাল স্টোরে কপি করেন, তাহলে এখন আপনাকে এগুলো সিংক রাখতে হবে যখন:\n\n- ব্যাকগ্রাউন্ড রিফেচ হয়\n- অন্য ট্যাব/ইউজার আপডেট করে\n- মিউটেশনের পরে পার্শিয়াল আপডেট আসে\n\nডিফল্ট নিয়ম: সার্ভার ডেটা এক জায়গা থেকে পড়ুন (একটি query cache) এবং শুধু UI-সংক্রান্ত বিষয় বা ড্রাফটের জন্য লোকাল স্টেট তৈরি করুন।
কেবল তখনই স্টোর করুন যখন আপনি সত্যিই এটা সহজে গণনা করতে না পারেন।\n\nসাধারণত আপনি বিদ্যমান ইনপুট থেকে গণনা করবেন:\n\n- visibleUsers = users.filter(...)\n- total = items.reduce(...)\n- canSubmit = isValid && !isSaving\n\nযদি পারফরম্যান্স সত্যিই সমস্যা হয়ে ওঠে (মাপা হয়েছে), তাহলে useMemo বা উন্নত ডাটা স্ট্রাকচার ব্যবহার করুন স্টেট রেখে দেওয়ার আগে।
ডিফল্ট: একটি সার্ভার-স্টেট টুল (সাধারণত TanStack Query) ব্যবহার করুন যাতে কম্পোনেন্টগুলো কেবল “ডেটা চায়” এবং লোডিং/এরর হ্যান্ডেল করতে পারে।\n\nপ্র্যাকটিক্যাল বিষয়ে:\n\n- স্থিতিশীল, কনসিস্টেন্ট query keys ব্যবহার করুন (শুধু সেই ইনপুটগুলো অন্তর্ভুক্ত করুন যা ফলাফল পরিবর্তন করে)।\n- লেখার জন্য মিউটেশন ব্যবহার করুন এবং প্রতি ফিচারের জন্য একটি স্ট্র্যাটেজি বেছে নিন:\n - ঠিক সে লিস্ট/ডিটেইল query কে invalidate করুন, অথবা\n - ক্যাশে টার্গেটেড আপডেট করুন।\n\nপুড়োটা জায়গায় refetch() ছড়িয়ে দেওয়া থেকে বিরত থাকুন “নিশ্চিত থাকার” জন্য।
লোকালেই রাখুন যতক্ষণ না আপনার কাছে একটি বাস্তব শেয়ারিং প্রয়োজন এর নাম দেয়া যায়।\n\nপ্রমোশন নিয়ম:\n\n- ডিফল্টে লোকাল component state।\n- সিবলিংস যখন শেয়ার করবে তখন সর্বনিম্ন কমন প্যারেন্টে লিফট করুন।\n- যখন বহু দূরের কম্পোনেন্ট/রুট একই সময়ে প্রয়োজন করবে তখনই একটি ছোট শেয়ার্ড স্টোর ব্যবহার করুন।\n\nএভাবে আপনার গ্লোবাল স্টোর অপ্রয়োজনীয় ফ্ল্যাগের গুদাম হয়ে উঠবে না।
ID এবং ছোট ফ্ল্যাগগুলো স্টোর করুন—পূর্ণ সার্ভার অবজেক্ট নয়।\n\nউদাহরণ:\n\n- ✅ selectedUserId\n- ❌ selectedUser (কপি করা অবজেক্ট)\n\nতারপর ডিটেইল রেন্ডার করার সময় ক্যাশ বা লিস্ট থেকে ইউজার লুকআপ করুন। ব্যাকগ্রাউন্ড রিফেচ ও আপডেটগুলি অতিরিক্ত সিঙ্কিং কোড ছাড়া ঠিক কাজ করবে।
ফর্মকে সাবমিট না করা পর্যন্ত ড্রাফট হিসেবে বিবেচনা করুন (client state)।\n\nপ্র্যাকটিক্যাল প্যাটার্ন:\n\n- সার্ভারে থাকা রেকর্ড ফেচ করুন।\n- সেটির থেকে লোকাল ড্রাফট ইনিশিয়ালাইজ করুন।\n- ইউজার ড্রাফট স্বাধীনভাবে এডিট করতে দেয়া।\n- সেভ করলে ড্রাফট সাবমিট করুন এবং তারপর সার্ভার ক্যাশ আপডেট/রিফ্রেশ করুন।\n- ক্যান্সেল করলে ড্রাফট বাদ দিন এবং সার্ভার ভার্সন দেখান।\n\nএতে আপনি সার্ভার ডেটাকে “ইন-প্লেস” এডিট করে রিফেচের সাথে লড়াই করবেন না।
কমন রেড ফ্ল্যাগসমূহ:\n\n- একই ডেটা কম্পোনেন্ট, গ্লোবাল স্টোর, এবং query ক্যাশে তিন জায়গায় বিদ্যমান।\n- এফেক্টগুলো যাতে স্টেট একে অপরের সাথে মিলায় ("when query changes, set store", "when store changes, refetch")।\n- needsRefresh, didInit, isSaving মতো শেয়ার করা ফ্ল্যাগ buildup।\n- আপনি এক বাক্যেই বলতে না পারা—“এই ভ্যালুটি কোথা থেকে আসে?”\n\nসমাধান সাধারণত নতুন লাইব্রেরি নয়—প্রতি মানের জন্য একটি মালিক নির্ধারণ করুন এবং কপি গুলো মুছুন।
জেনারেটেড স্ক্রিনগুলো দ্রুত বাড়তে পারে, এবং প্রতিটি নতুন স্ক্রিন আলাদা স্টেট সিদ্ধান্ত আনতে পারে। একটি সহজ সুরক্ষা হল মালিকানা স্ট্যান্ডার্ড করা:\n\n- Server state: সবসময় একই fetching/caching লেয়ারের মাধ্যমে।\n- Client UI state: ডিফল্টে লোকাল, প্রয়োজন হলে স্টোর।\n- Derived values: গণনা করে নিন, সংরক্ষণ করবেন না।\n\nআপনি যদি Koder.ai ব্যবহার করেন, Planning Mode ব্যবহার করে জেনারেট করার আগে মালিকানা ঠিক করুন, এবং স্টেট পরিবর্তনের সময় স্ন্যাপশট/রোলব্যাক ব্যবহার করুন যাতে সমস্যা হলে ফিরে আসা যায়।