State এর স্ট্রাকচার ঠিকভাবে নির্বাচন করা

State Structure ঠিকভাবে বাছাই করা

State গঠন ভালোভাবে করা মানে হচ্ছে component টাকে এমনভাবে তৈরি করা, যাতে এটা পরবর্তীতে পরিবর্তন বা debug করা সহজ হয়। আর যদি state ঠিকভাবে গঠিত না হয়, তাহলে এটা bug এর কারখানায় পরিণত হতে পারে। নিচে কিছু গুরুত্বপূর্ণ পরামর্শ দেয়া হলো, যেগুলো মাথায় রাখা উচিত state গঠন করার সময়।

এই পাঠে শিখবেন-

  1. কখন একাধিক state variable ব্যবহার করা উচিত আর কখন একটা
  2. state গঠনের সময় কোন ভুলগুলো এড়িয়ে চলতে হবে
  3. সাধারণ state গঠনের সমস্যাগুলো কিভাবে ঠিক করা যায়

🧱 State Structure এর জন্য মূলনীতি

তুমি যখন একটি component লিখো যেটাতে কিছু state থাকবে, তখন তোমাকে সিদ্ধান্ত নিতে হয়:

  • কতগুলো state variable থাকবে?
  • state-এর ডেটা কেমন আকারে থাকবে?

হ্যাঁ, state-এর structure যদি ভালো না-ও হয়, তবুও technically তোমার কোড কাজ করবে — কিন্তু bug হওয়ার ঝুঁকি থাকবে অনেক বেশি।

তাই নিচের কিছু সহজ গাইডলাইন মনে রাখলে তুমি আরও ভালো state structure করতে পারবে:

১। সম্পর্কিত state একসাথে রাখো (Group related state together) ২। পরস্পর বিরোধী (contradictory) state এড়িয়ে চলো (Avoid contradictory state) ৩। Redundant State রাখো না (বাড়তি স্টেট এড়িয়ে চলো) ৪। State-এ একই ডেটা বারবার রাখা ঠিক না (Avoid duplication) ৫। Deeply nested state structure এড়িয়ে চলো (Avoid deeply nested state structure)


🎯 এই নিয়মগুলোর উদ্দেশ্য

এই গাইডলাইনগুলোর উদ্দেশ্য হলো:

"state এমনভাবে গঠন করা যাতে সেটা সহজে update করা যায়, কিন্তু ভুল না হয়।"

Redundant বা duplicate state remove করলে সবকিছু sync থাকে। এটা অনেকটা ডেটাবেস normalizing করার মতো — যেখানে আমরা একই তথ্য একাধিক জায়গায় না রেখে centralized রাখি।

Albert Einstein বলেছেন,

"Make your state as simple as it can be—but no simpler."

মানে যতটা সম্ভব সহজ রাখো, কিন্তু অতি সরল করে important logic হারিয়ে ফেলো না।


এখন আমরা দেখবো কিভাবে এই principle গুলো রিয়েল উদাহরণে কাজে লাগে।

✅ ১. সম্পর্কিত state একসাথে রাখো

যদি দেখো দুইটা বা তার বেশি state সবসময় একসাথে update হয়, তাহলে তাদের আলাদা না রেখে একটায় merge করে দাও।

// ভুল: আলাদা স্টেট
const [x, setX] = useState(0);
const [y, setY] = useState(0);
 
// ভালো: একত্রে রাখা
const [position, setPosition] = useState({ x: 0, y: 0 });

প্রয়োজনে একাধিক useState ভেংগে একটি সিংগেল state এ রাখুন/ অবজেক্ট/ array হিসেবে রাখুন

কখন একাধিক state রাখবো, আর কখন একটাই যথেষ্ট?

ধরো, তুমি x আর y position ট্র্যাক করতে চাও। এখন প্রশ্ন হলো, এইভাবে আলাদা আলাদা করবো?

const [x, setX] = useState(0);
const [y, setY] = useState(0);

না কি একসাথেই রাখবো?

const [position, setPosition] = useState({ x: 0, y: 0 });

👉 দুটোই technically ঠিক, কিন্তু যদি দেখো x আর y সবসময় একসাথে পরিবর্তন হচ্ছে — তাহলে একটাই স্টেট রাখা ভালো। এতে করে দুটোকে আলাদা আলাদা update করতে ভুল হবার চান্স কমে যায়।

উদাহরণ: মাউস পয়েন্টার নড়ার সাথে সাথে একটা লাল বল পজিশন চেঞ্জ করে —

import { useState } from "react";
 
export default function MovingDot() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
 
  return (
    <div
      onPointerMove={(e) => {
        setPosition({
          x: e.clientX,
          y: e.clientY,
        });
      }}
      style={{
        position: "relative",
        width: "100vw",
        height: "100vh",
      }}
    >
      <div
        style={{
          position: "absolute",
          backgroundColor: "red",
          borderRadius: "50%",
          transform: `translate(${position.x}px, ${position.y}px)`,
          left: -10,
          top: -10,
          width: 20,
          height: 20,
        }}
      />
    </div>
  );
}

কখন object বা array হিসেবে স্টেট রাখবো?

যখন জানো না কতগুলো state লাগবে (যেমন: ডাইনামিক form), তখন একটা object বা array হিসেবে রাখা ভালো।


⚠️ সাবধানতা:

যদি state variable একটা object হয়, তাহলে একটা ফিল্ড আপডেট করতে চাইলে বাকি ফিল্ডগুলো কপি করে রাখতে হবে। নিচেরটা ভুল:

setPosition({ x: 100 }); // ❌ y হারিয়ে যাবে

ঠিক উপায় হলো:

setPosition({ ...position, x: 100 }); // ✅ y থাকবে

অথবা x, y আলাদা আলাদা state করলে আলাদা করে সেট করা যায়।

⚠️ ২. পরস্পর বিরোধী (contradictory) state এড়িয়ে চলো

যদি তোমার state এমনভাবে গঠিত হয় যে, একটার সাথে আরেকটার মান conflict করে (মানে একে অপরের সাথে mismatch হয়), তাহলে bug আসার চান্স বেশি।

উদাহরণ:

// খারাপ
const [isLoggedIn, setIsLoggedIn] = useState(true);
const [user, setUser] = useState(null); // তাহলে ইউজার কোথায়?

--- এটা একটা impossible state কারণ তুমি logged in আছো কিন্তু ইউজার নেই। এটা একটা contradiction।

ধরো, তুমি একটা hotel feedback form বানালে যেখানে দুটি state variable আছে — isSending আর isSent:

const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);

এগুলো দিয়ে তুমি বোঝাতে চাও: ফর্মটা পাঠানো হচ্ছে কিনা (isSending) আর পাঠানো হয়ে গেছে কিনা (isSent)।

কিন্তু সমস্যা কী?

যদি ভুলে যাও কখন setIsSent(true) আর কখন setIsSending(false) দিতে হবে — তাহলে isSending: true আর isSent: true দুইটায় একসাথে true হয়ে যেতে পারে! এটা একটা impossible state কারণ ফর্ম কখনোই "পাঠানো হচ্ছে" এবং "পাঠানো হয়ে গেছে" — এই দুই অবস্থা একসাথে থাকতে পারে না।

যতো বড় কম্পোনেন্ট হবে, এই টাইপের contradiction-tracking করা ততো কষ্টকর।


✅ সলিউশন: একটা স্টেটই রাখো status নামে

const [status, setStatus] = useState("typing");

এই status স্টেট নিচের ৩টা অবস্থা ধরে রাখতে পারবে:

  • 'typing' → ইউজার লিখতেছে
  • 'sending' → ফর্ম সাবমিট হইতেছে
  • 'sent' → ফর্ম পাঠানো হয়ে গেছে

এতে অবস্থা গুলা স্পষ্ট থাকে আর ভুল হওয়ার চান্সও কমে যায়।


✨ Updated Version:

import { useState } from "react";
 
export default function FeedbackForm() {
  const [text, setText] = useState("");
  const [status, setStatus] = useState("typing");
 
  async function handleSubmit(e) {
    e.preventDefault();
    setStatus("sending");
    await sendMessage(text);
    setStatus("sent");
  }
 
  const isSending = status === "sending";
  const isSent = status === "sent";
 
  if (isSent) {
    return <h1>Thanks for feedback!</h1>;
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <p>How was your stay at The Prancing Pony?</p>
      <textarea
        disabled={isSending}
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <br />
      <button disabled={isSending} type="submit">
        Send
      </button>
      {isSending && <p>Sending...</p>}
    </form>
  );
}
 
function sendMessage(text) {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000);
  });
}

✅ উপকার কী?

  • স্টেটগুলা একে অপরের সাথে clash করবে না।
  • isSending আর isSent একসাথে true হবার চান্স নাই।
  • কোডটাও বেশি readable, কম বাগপ্রোন।

Einstein এর কথা মনে রেখো:

(আপনার state যতটা সম্ভব সরল রাখুন—কিন্তু অতিরিক্ত সরল না করে।)

ভিডিও লিংক: https://learnwithsumit.com/rnext/courses/rnext/choosing-the-state-structure-avoid-redundant-state (opens in a new tab)

♻️ ৩. Redundant State রাখো না (বাড়তি স্টেট এডিয়ে চলো)

যদি কোনো মান component-এর props বা অন্য state দিয়েই হিসাব করা যায়, তাহলে আলাদা করে state-এ রাখার দরকার নাই।

// ভুল
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
 
// ঠিক
const fullName = firstName + " " + lastName;
যদি কোনো তথ্য component-এর props বা state থেকেই render-এর সময় হিসেব করে ফেলা যায়, তাহলে সেটা আলাদা করে state-এ রাখা **প্রয়োজন নাই**
 
### উদাহরণ:
 
```js
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");

এখানে fullName রিডান্ড্যান্ট কারণ তুমি এটা firstName + ' ' + lastName থেকে প্রতি render-এ বের করতে পারো।

✅ সঠিকভাবে করলে এমন হবে:

const fullName = firstName + " " + lastName;

এতে fullName এর জন্য state হ্যান্ডেল করতে হয় না, বাগের চান্স কমে, কোড হয় ক্লিন।


⚠️ প্রপসকে স্টেটে কপি কোরো না

const [color, setColor] = useState(messageColor); // ❌

এইভাবে প্রপসকে স্টেটে কপি করলে পরবর্তীতে প্রপস আপডেট হলেও state আপডেট হবে না।

✅ সঠিক:

const color = messageColor; // 👍

তবে, যদি শুধু প্রথমবারের জন্য প্রপস দরকার হয়, তখন ঠিক আছে:

const [color, setColor] = useState(initialColor); // ✅

এখানে আমরা বুঝিয়ে দিচ্ছি future changes ignore করবো।

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

❌ প্রপসকে mirror করে state বানানো যাবে না

নিচের কোডটা ভুলের একটা উদাহরণ:

function Message({ messageColor }) {
  const [color, setColor] = useState(messageColor);
}

এখানে state হিসেবে color কে props থেকে সেট করা হয়েছে। কিন্তু যদি parent পরে prop পরিবর্তন করে, state আপডেট হবে না। কারণ useState() শুধু প্রথম render এ মান নেয়।

✅ এর বদলে সরাসরি prop ব্যবহার করো:

function Message({ messageColor }) {
  const color = messageColor;
}

📝 কখন props থেকে state বানানো যায়?

যখন তুমি চাও parent থেকে নতুন props এলেও সেটা ignore হবে, তখন করা যায়। সেক্ষেত্রে prop এর নাম convention মতো initial বা default দিয়ে শুরু করো:

function Message({ initialColor }) {
  const [color, setColor] = useState(initialColor);
}

এখানে বোঝানো হচ্ছে, color state শুধুমাত্র প্রথম initialColor এর মান নিবে, পরবর্তী আপডেট গুলা ইনগোর করবে।


🧠 সারাংশ

ভালো প্র্যাকটিস ✅খারাপ প্র্যাকটিস ❌
হিসাবযোগ্য মানগুলো render time এ হিসাব করোহিসাবযোগ্য মানকে state এ রাখো না
প্রপসকে সরাসরি ব্যবহার করোপ্রপসকে mirror করে state বানিও না
initialProp নামে বুঝিয়ে দাও future update দরকার নাইprop এর নাম সাধারণ রাখলে ভুল বোঝাবুঝি হয়

Bottom line:
👉 যা হিসাব করে বের করা যায়, তা state-এ রেখো না।
👉 প্রপসকে কখনোই অকারণে স্টেটে কপি কোরো না।

🚫 ৪. State-এ একই ডেটা বারবার রাখা ঠিক না (Avoid duplication)

একই data যদি একাধিক জায়গায় রাখা হয়, তাহলে এক জায়গা change করলে অন্য জায়গায় manually update করতে হয়। এতে mismatch বা bug আসতে পারে।

// খারাপ
const [user, setUser] = useState({ name: "Mojnu", email: "a@a.com" });
const [userName, setUserName] = useState("Mojnu"); // Duplication!
 
// ভালো
const userName = user.name;

এখানে userName কে আলাদা state হিসেবে রাখার দরকার নাই। তুমি user object থেকেই বের করতে পারো।

🧱 ৫. Deeply Nested State এড়িয়ে চলুন — কেন? কিভাবে?

React এ যখন আমরা অনেক ভিতরের ভিতরে ডেটা রাখি, মানে nested object বা array — তখন ওই ডেটা আপডেট করা খুব কঠিন হয়ে যায়।

যেমন:

// ❌ খারাপ
const [form, setForm] = useState({ user: { address: { city: "Dhaka" } } });

এই স্ট্রাকচার এ city আপডেট করতে গেলে পুরা object কপি করে করতে হয়।

তাই সবচেয়ে ভালো হয় যদি নিচের মতো করে ফ্ল্যাট স্ট্রাকচারে রাখি:

// ✅ ভালো
const [city, setCity] = useState("Dhaka");

🔍 Nested structure এ সমস্যা কোথায়?

ধরুন আপনি এমন একটা state রেখেছেন যেখানে "Botswana" আছে এইভাবে:

Root > Earth > Africa > Botswana

এখন যদি আপনি শুধু Botswana মুছতে চান, তাহলে আপনাকে Africa, Earth, এমনকি Root অব্দিও নতুন করে কপি করে আপডেট করতে হবে। মানে, একটা ছোট কাজের জন্য অনেক লাইন কোড লিখতে হয়।

এটাই Nested State এর সমস্যার মূল কারণ।


✅ সমাধান: ফ্ল্যাট স্ট্রাকচার (Flat Structure)

আমরা যদি প্রতিটি জায়গাকে আলাদা object হিসেবে রাখি এবং তার childIds আলাদাভাবে রাখি, তাহলে:

  • আপডেট করা সহজ হয়
  • delete বা find করাও সহজ হয়
  • performance ভালো থাকে
  • কম্পোনেন্ট গুলোও পরিষ্কার হয়

এটা অনেকটা Database টেবিলের মতো — যেখানে প্রতিটা row আলাদা ID দিয়ে চেনা যায়।


❌ Nested structure (ক্লাসিক পদ্ধতি)

const places = {
  id: 0,
  title: "(Root)",
  childPlaces: [
    {
      id: 1,
      title: "Earth",
      childPlaces: [
        {
          id: 2,
          title: "Africa",
          childPlaces: [
            {
              id: 3,
              title: "Botswana",
              childPlaces: [],
            },
          ],
        },
      ],
    },
  ],
};

এখানে Botswana খুঁজে পেতে গেলে অনেক ধাপ পেরোতে হয়।


✅ ফ্ল্যাট structure (দারুন সহজ)

{
  0: {
    id: 0,
    title: '(Root)',
    childIds: [1]
  },
  1: {
    id: 1,
    title: 'Earth',
    childIds: [2]
  },
  2: {
    id: 2,
    title: 'Africa',
    childIds: [3]
  },
  3: {
    id: 3,
    title: 'Botswana',
    childIds: []
  },
}

এখানে শুধু ID দিলেই আপনি যেকোনো জায়গায় পৌঁছে যেতে পারবেন: placesById[3]


🎁 ফ্ল্যাট স্ট্রাকচারের সুবিধা:

  • কোনো প্লেস খুঁজতে বা মুছতে ID ই যথেষ্ট
  • কম কোডে অনেক কাজ হয়
  • বড় ডেটা হ্যান্ডেল করাও সহজ
  • পারফরম্যান্স অনেক ভালো

🧩 Component Breakdown

PlaceTree Component

function PlaceTree({ id, placesById }) {
  const place = placesById[id];
  const childIds = place.childIds;
 
  return (
    <li>
      {place.title}
      {childIds.length > 0 && (
        <ol>
          {childIds.map((childId) => (
            <PlaceTree key={childId} id={childId} placesById={placesById} />
          ))}
        </ol>
      )}
    </li>
  );
}

📌 এটা একটা recursive component — মানে নিজেই নিজেকে কল করে nested child গুলো দেখায়।


TravelPlan Component

export default function TravelPlan() {
  const [plan, setPlan] = useState(initialTravelPlan);
  const root = plan[0];
  const planetIds = root.childIds;
 
  return (
    <>
      <h2>Places to visit</h2>
      <ol>
        {planetIds.map((id) => (
          <PlaceTree key={id} id={id} placesById={plan} />
        ))}
      </ol>
    </>
  );
}

📌 এখানে মূল state রাখা হয়েছে plan নামে — যেটা হচ্ছে একটা ফ্ল্যাট object যেখানে প্রতিটা প্লেস আলাদা key হিসেবে আছে।


📂 initialTravelPlan (ডেটার structure)

export const initialTravelPlan = {
  0: {
    id: 0,
    title: "(Root)",
    childIds: [1, 42, 46],
  },
  1: {
    id: 1,
    title: "Earth",
    childIds: [2, 10, 19, 26, 34],
  },
  2: {
    id: 2,
    title: "Africa",
    childIds: [3, 4, 5, 6, 7, 8, 9],
  },
};

📌 প্রতিটা জিনিস আলাদা key, আর তার অধীনে childIds এ child id গুলো রাখা হয়েছে।


🧪 উপসংহার

Flat structure কেন বেস্ট?

  • আপডেট, খোঁজা, বা ডিলিট সব সহজ হয়
  • বড় ডেটাও efficiently manage করা যায়
  • কম্পোনেন্ট clean ও readable হয়
  • পারফরম্যান্স অনেক উন্নত হয়

🔧 Bonus: Memory অপটিমাইজ + Immer দিয়ে smart update

Immer লাইব্রেরি দিয়ে আমরা state ইমিউটেবল রেখে অনেক সহজে update করতে পারি। নিচে একটা example:

import { useImmer } from "use-immer";
 
const [plan, updatePlan] = useImmer(initialTravelPlan);
 
function handleComplete(parentId, childId) {
  updatePlan((draft) => {
    const parent = draft[parentId];
    parent.childIds = parent.childIds.filter((id) => id !== childId);
 
    deleteAllChildren(childId);
    function deleteAllChildren(id) {
      const place = draft[id];
      place.childIds.forEach(deleteAllChildren);
      delete draft[id];
    }
  });
}

👉 এখানে handleComplete() কল করলে শুধু UI না — মেমোরিতেও সেই প্লেস পুরোপুরি delete হয়ে যায়।
👉 এইভাবে আমাদের অ্যাপ স্মার্টভাবে কাজ করে, memory leak হয় না।


এক কথায়:
Nested state মানেই ঝামেলা।
Flat state মানেই সুন্দর, সহজ, স্মার্ট কোড।