আপনি যদি React নিয়ে কাজ করে থাকেন, তবে আপনার কনসোলে হলুদ রঙের একটি ওয়ার্নিং অবশ্যই দেখেছেন: “Each child in a list should have a unique ‘key’ prop.” এটি শুনতে একটি ভদ্র পরামর্শের মতো মনে হলেও, React আসলে আপনাকে সতর্ক করছে যে এটি আপনার লিস্ট আইটেমগুলোর মধ্যে পার্থক্য করতে পারছে না। এটি অবহেলা করলে আপনি এমন একটি বাগ তৈরি করবেন যা খুঁজে বের করা বা পুনরায় তৈরি করা অত্যন্ত বিরক্তিকর হবে—যেমন স্টেট ভুল রো-তে চলে যাওয়া, টেক্সট ইনপুট ফোকাস হারানো, অথবা ভুল এলিমেন্টে অ্যানিমেশন চলা।

React-এর রেন্ডারিং ইঞ্জিন আপনার UI পিক্সেল বাই পিক্সেল তুলনা করে না। এটি ভার্চুয়াল DOM নামক অবজেক্টের একটি হালকা ওজনের ট্রি (tree) তৈরি করে, নতুন ট্রি-টির সাথে আগেরটির তুলনা করে এবং রিয়েল DOM-এর জন্য প্রয়োজনীয় পরিবর্তনের ক্ষুদ্রতম সেটটি গণনা করে। যখন আপনি একটি লিস্ট রেন্ডার করেন, React সেখানে সিবলিং (sibling) এলিমেন্টের একটি অ্যারে দেখতে পায়। কী (key) ছাড়া, কোনো আইটেম সরানো হয়েছে, প্রতিস্থাপিত হয়েছে নাকি মুছে ফেলা হয়েছে তা জানার কোনো নির্ভরযোগ্য উপায় তার নেই। এটি ডিফল্টভাবে পজিশন বা অবস্থান অনুযায়ী ম্যাচ করার চেষ্টা করে, যা অত্যন্ত ভঙ্গুর। কী (key) একটি স্থিতিশীল পরিচয় (stable identity) হিসেবে কাজ করে। এগুলো React-কে বলে, “এই এলিমেন্টটি আগেরটিরই অংশ, এমনকি যদি এটি এখন অন্য কোনো স্লটে থাকে তবুও।” এটি ভুল করলে আপনি সুনির্দিষ্ট আপডেটের বদলে অনুমানের ওপর নির্ভর করতে বাধ্য হবেন।

ন্যূনতম সমাধান

এই ওয়ার্নিংটি সাধারণত একটি map কলের ভেতরে দেখা যায়। আপনাকে ইটারেটর থেকে রিটার্ন করা টপ-লেভেল এলিমেন্টে একটি ইউনিক ভ্যালু key অ্যাট্রিবিউট হিসেবে অ্যাসাইন করতে হবে।

প্রতিটি কোডবেসে এই প্যাটার্নটি দেখা যায় যা ওয়ার্নিং ট্রিগার করে:

const UserList = ({ users }) => {
  return (
    <ul>
      {users.map((user) => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};

React তিনটি <li> ট্যাগ দেখতে পায় কিন্তু কোনটি কোনটি তা বুঝতে পারে না। এর সমাধান হলো একটি অ্যাট্রিবিউট যোগ করা:

const UserList = ({ users }) => {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

map কলব্যাকের ভেতরে থাকা এলিমেন্টে সরাসরি key অ্যাসাইন করতে হবে। আপনি যদি <li> ট্যাগটিকে একটি আলাদা UserItem কম্পোনেন্টে নিয়ে যান, তবুও কী (key) টি কল সাইটে কম্পোনেন্টের ওপর থাকতে হবে:

{users.map((user) => (
  <UserItem key={user.id} user={user} />
))}

UserItem-এর ভেতরে থাকা ইন্টারনাল <div>-এ কী (key) রাখলে ওয়ার্নিংটি যাবে না এবং রিকনসিলিয়েশন (reconciliation) আচরণও ঠিক হবে না। React ইটারেটর দ্বারা রিটার্ন করা এলিমেন্টের ওপর কী (key) খোঁজে।

কেন ইনডেক্স (Index) কী হিসেবে ব্যবহার করা বিপজ্জনক

map-এর দ্বিতীয় আর্গুমেন্ট ব্যবহার করে ওয়ার্নিংটি থামিয়ে দেওয়া খুব সহজ মনে হতে পারে:

{users.map((user, index) => (
  <li key={index}>{user.name}</li>
))}

এটি কনসোলের নোয়েজ কমিয়ে দিলেও মূল সমস্যাটি সমাধান করে না। অ্যারে ইনডেক্স কোনো পরিচয় (identity) নয়। এগুলো হলো অবস্থান (position), আর অবস্থান পরিবর্তনশীল।

কল্পনা করুন তিনটি ইউজারের একটি লিস্ট এই ক্রমে রেন্ডার করা হয়েছে:

  1. Alice (index 0)
  2. Bob (index 1)
  3. Charlie (index 2)

আপনি যদি Alice-কে ডিলিট করেন, তবে Bob ইনডেক্স 0-তে চলে আসবে এবং Charlie ইনডেক্স 1-এ চলে আসবে। React নতুন ট্রি-টির সাথে পুরনো ট্রি-টির তুলনা করে। এটি দেখে যে ইনডেক্স 0 এখন Bob-এর ডেটা ধারণ করছে, তাই এটি বিদ্যমান DOM নোডটিকে পরিবর্তন (mutate) করে যা আগে Alice-কে দেখাচ্ছিল। যদি সেই নোডটিতে ফোকাস থাকে, তবে কার্সারটি প্রথম রো-তেই থেকে যাবে, কিন্তু টেক্সট পরিবর্তিত হয়ে Bob হয়ে যাবে। যদি সেই রো-তে লোকাল স্টেটসহ একটি <input> থাকে, তবে সেই স্টেটটি ইনডেক্স 0-এর সাথেই আটকে থাকবে। ইউজার এমন একটি রো-তে টাইপ করবেন যা দেখতে Bob-এর রো বলে মনে হয়, কিন্তু স্টেটটি ছিল Alice-এর। লিস্ট সর্ট (sort), ফিল্টার (filter) বা নতুন আইটেম যোগ করার সময়ও একই বিশৃঙ্খলা ঘটে। ইনডেক্স কী ব্যবহারের একমাত্র নিরাপদ জায়গা হলো এমন একটি লিস্ট যা সম্পূর্ণ স্ট্যাটিক (static): যেখানে কোনো রিয়ার্ডারিং, ফিল্টারিং, ইনসার্টিং বা ডিলিটিং হবে না। হার্ডকোডেড নেভিগেশন লিঙ্ক যা কখনো পরিবর্তন হয় না, তার একটি ভালো উদাহরণ। অন্য সবকিছুর জন্য একটি প্রকৃত আইডেন্টিফায়ার প্রয়োজন।

একটি স্থিতিশীল কী (Stable Key) কোথায় পাবেন

সেরা কী হলো একটি ইউনিক আইডেন্টিফায়ার যা ইতিমধ্যে আপনার ডেটা মডেলে বিদ্যমান। ডেটাবেসের প্রাইমারি কী (primary key) যেমন id আদর্শ, কারণ এগুলো নিশ্চিতভাবে ইউনিক এবং রেন্ডারের পরেও অপরিবর্তিত থাকে। যদি আপনার ব্যাকএন্ড uuid, slug, বা অন্য কোনো প্রাকৃতিকভাবে ইউনিক ফিল্ডসহ অবজেক্ট রিটার্ন করে, তবে সেটি ব্যবহার করুন।

যখন আপনার API রেসপন্সে কোনো ইউনিক ফিল্ড থাকে না, তখন আপনার কাছে দুটি ব্যবহারিক পথ রয়েছে। প্রথমত, আপনার ব্যাকএন্ড টিমের সাথে কথা বলুন এবং একটি id অন্তর্ভুক্ত করতে বলুন। প্রাইমারি কী ছাড়া রিলেশনাল ডেটা পাঠানো একটি ত্রুটিপূর্ণ পদ্ধতি, এবং সোর্স থেকে এটি ঠিক করলে আপনার পুরো স্ট্যাকে অস্পষ্টতা দূর হবে। দ্বিতীয়ত, আপনি যদি সম্পূর্ণ ক্লায়েন্ট সাইডে আইটেম তৈরি করেন—যেমন একটি টু-ডু লিস্ট যেখানে ইউজার সার্ভারে যাওয়ার আগেই টাস্ক তৈরি করে—তবে তৈরির সময় একবার একটি ID তৈরি করে নিন। uuid বা nanoid-এর মতো লাইব্রেরিগুলো ঠিক এই কাজের জন্যই তৈরি করা হয়েছে। ইউজার যখন ফর্ম সাবমিট করবেন তখন ID তৈরি করুন, এটি অবজেক্টে সংরক্ষণ করুন এবং চিরকাল কী (key) হিসেবে ব্যবহার করুন।

রেন্ডার পাথের (render path) ভেতরে কখনোই কী (key) তৈরি করবেন না। কম্পোনেন্ট রেন্ডারের সময় Math.random() বা Date.now() কল করলে প্রতিবার একটি নতুন ভ্যালু তৈরি হয়। React একটি নতুন কী দেখে, ধরে নেয় এটি একটি সম্পূর্ণ নতুন এলিমেন্ট, পুরনো DOM নোডটি ধ্বংস করে এবং একটি নতুনটি তৈরি করে। সেই এলিমেন্টের ভেতরে থাকা যেকোনো স্টেট রিসেট হয়ে যায়। ফোকাস হারিয়ে যায়। পারফরম্যান্স মারাত্মকভাবে কমে যায় কারণ React অপ্রয়োজনীয় DOM কাজ করতে থাকে। একটি র‍্যান্ডমলি জেনারেট করা কী থাকা মানে কী না থাকার চেয়েও খারাপ।

Fragments, Components, and Scope

একটি কম বোঝা বা সূক্ষ্ম ফাঁদ হলো React Fragments। আপনি যদি কোনো ডেটার ওপর map চালান এবং কোনো wrapper <div> ছাড়াই একাধিক sibling element রিটার্ন করতে চান, তবে আপনি হয়তো শর্ট সিনট্যাক্স (short syntax) ব্যবহার করতে চাইবেন:

{items.map((item) => (
  <>
    <dt>{item.term}</dt>
    <dd>{item.definition}</dd>
  </>
))}

শর্টহ্যান্ড <>...</> কোনো props সাপোর্ট করে না, যার মানে হলো আপনি এতে কোনো key যুক্ত করতে পারবেন না। এই ক্ষেত্রে, পূর্ণাঙ্গ explicit syntax ব্যবহার করুন:

{items.map((item) => (
  <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.definition}</dd>
  </React.Fragment>
))}

React-এর Fragment-এ ওই key-টি প্রয়োজন যাতে এটি রেন্ডারগুলোর (renders) মাধ্যমে জোড়াটিকে একটি একক ইউনিট হিসেবে ট্র্যাক করতে পারে।

আরেকটি সূক্ষ্ম বিষয়: key সাধারণ অর্থে কোনো prop নয়। আপনি যদি <ListItem key={item.id} /> লেখেন, তবে ListItem component props.key পড়তে পারবে না। React এটি অভ্যন্তরীণ বুককিপিংয়ের (bookkeeping) জন্য ব্যবহার করে। যদি আপনার component-এর নিজস্ব লজিকের জন্য ওই identifier-টি প্রয়োজন হয়, তবে অন্য কোনো নামে আলাদাভাবে সেটি পাস করুন, যেমন itemId

নিজেকে নিরাপদ রাখতে কিছু ব্যবহারিক নিয়ম

  • Database ID ব্যবহার করা শ্রেয়। এগুলো unique, সংখ্যা বা string-ভিত্তিক এবং stable।
  • Client-only ডেটার জন্য uuid বা nanoid ব্যবহার করুন। রেকর্ডটি তৈরি করার সময় একবার ID জেনারেট করুন, component render-এর ভেতরে নয়।
  • যদি লিস্ট পরিবর্তন হওয়ার সম্ভাবনা থাকে, তবে কখনোই array index থেকে key তৈরি করবেন না। Sorting, filtering এবং deleting করার ফলে visual এবং state সংক্রান্ত bug দেখা দিতে পারে।
  • কখনোই Math.random(), Date.now(), বা রেন্ডারের মাঝে পরিবর্তন হয় এমন কোনো value ব্যবহার করবেন না। এটি অপ্রয়োজনীয় unmounting এবং remounting ঘটায়।
  • মনে রাখবেন, Fragments-এর ক্ষেত্রে long form ব্যবহার করতে হবে যদি সেগুলো কোনো map-এর ভেতরে থাকে এবং একটি key-এর প্রয়োজন হয়।
  • Key-টি map দ্বারা রিটার্ন করা element-এ রাখুন, কোনো child component-এর ভেতরে নয়।

মূল শিক্ষা

key prop কোনো সাজসজ্জার জন্য দেওয়া lint rule নয়। এটি হলো এমন একটি পদ্ধতি যার মাধ্যমে React রেন্ডারগুলোর মধ্যে identity বজায় রাখে। এটিকে একটি ডাটাবেস টেবিলের primary key-এর মতো ভাবুন। যখন ওই identity stable থাকে, তখন React নিখুঁতভাবে element-গুলোকে move, update এবং remove করতে পারে। যখন এটি অনুপস্থিত বা unstable থাকে, তখন আপনাকে corrupted UI state এবং sluggish reconciliation-এর মাসুল দিতে হয়। ডাটা লেয়ারে (data layer) একবার এটি ঠিক করে ফেলুন, তাহলে আপনার লিস্টগুলো যত বড় বা পরিবর্তনশীলই হোক না কেন, সেগুলো একইভাবে কাজ করবে।