If you have spent any time in React, you have seen the yellow warning in your console: “Each child in a list should have a unique ‘key’ prop.” It sounds like a polite suggestion, but React is actually warning you that it cannot tell your list items apart. Ignore it, and you will eventually ship a bug that is maddening to reproduce—state jumping to the wrong row, text inputs losing focus, or animations firing on the wrong element.
React’s rendering engine does not compare your UI pixel by pixel. It builds a lightweight tree of objects called the virtual DOM, compares the new tree to the previous one, and calculates the smallest set of changes needed for the real DOM. When you render a list, React sees an array of sibling elements. Without keys, it has no reliable way to know whether an item moved, was replaced, or was removed. It defaults to matching by position, which is fragile. Keys act as stable identities. They tell React, “This element is the same one as before, even if it is now in a different slot.” Get this wrong, and you trade deterministic updates for guesswork.
The Minimum Fix
The warning usually appears inside a map call. You must assign a unique value to the key attribute on the top-level element returned from the iterator.
Here is the pattern you see in every codebase that triggers the warning:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li>
))}
</ul>
);
};
React sees three <li> tags and has no idea which is which. The correction is one attribute:
const UserList = ({ users }) => {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
The key must be assigned to the element directly inside the map callback. If you extract the <li> into a separate UserItem component, the key still belongs on the component at the call site:
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
Placing the key inside UserItem on its internal <div> will not silence the warning and will not fix the reconciliation behavior. React looks for the key on the element returned by the iterator.
Why Index as a Key Is Dangerous
It is tempting to silence the warning by reaching for the second argument of map:
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
This removes the console noise, but it does not solve the underlying problem. Array indices are not identities. They are positions, and positions change.
Imagine a list of three users rendered in this order:
- Alice (index 0)
- Bob (index 1)
- Charlie (index 2)
If you delete Alice, Bob shifts to index 0 and Charlie shifts to index 1. React compares the new tree to the old tree. It sees that index 0 now holds Bob’s data, so it mutates the existing DOM node that previously showed Alice. If that node had focus, the cursor stays in the first row, but the text changes to Bob. If the row contained an <input> with local state, that state remains stuck to index 0. The user types into what looks like Bob’s row, but the state belonged to Alice. The same chaos happens when you sort, filter, or prepend items. The only safe place for an index key is a list that is truly static: no reordering, no filtering, no inserting, and no deleting. Hardcoded navigation links that never change are a fair example. Everything else needs a real identifier.
Where to Find a Stable Key
The best key is a unique identifier that already exists in your data model. Database primary keys like id are ideal because they are guaranteed unique and they survive across renders. If your backend returns objects with uuid, slug, or some other naturally unique field, use that instead.
When your API response lacks any unique field, you have two practical paths. First, talk to your backend team and ask them to include an id. Shipping relational data without a primary key is a smell, and fixing it at the source removes ambiguity everywhere in your stack. Second, if you are generating items entirely on the client side—say, a todo list where users create tasks before anything hits the server—generate an ID once at creation time. Libraries like uuid or nanoid are built for exactly this. Generate the ID when the user submits the form, store it on the object, and use it for the key forever.
Never generate a key inside the render path. Calling Math.random() or Date.now() during a component render produces a new value on every pass. React sees a new key, assumes it is a brand new element, destroys the old DOM node, and creates a fresh one. Any state inside that element resets. Focus is lost. Performance tanks because React is doing unnecessary DOM work. A randomly generated key is worse than no key at all.
Fragments, Components, and Scope
കുറച്ചുകൂടി സൂക്ഷ്മമായ ഒരു കെണി React Fragments-മായി ബന്ധപ്പെട്ടതാണ്. നിങ്ങൾ ഡാറ്റയിലൂടെ മാപ്പ് (map) ചെയ്യുമ്പോൾ ഒരു വെപ്പർ <div> ഇല്ലാതെ തന്നെ ഒന്നിലധികം സിബ്ലിംഗ് (sibling) എലമെന്റുകൾ തിരികെ നൽകേണ്ടി വന്നാൽ, നിങ്ങൾ ഷോർട്ട് സിന്റാക്സ് (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>
))}
റെൻഡറുകൾക്കിടയിൽ (renders) ആ ജോഡിയെ ഒരു യൂണിറ്റായി ട്രാക്ക് ചെയ്യാൻ ഫ്രാഗ്മെന്റിൽ (Fragment) ആ കീ ആവശ്യമാണ്.
മറ്റൊരു പ്രധാന കാര്യം: കീകൾ സാധാരണ അർത്ഥത്തിലുള്ള പ്രോപ്സല്ല. നിങ്ങൾ <ListItem key={item.id} /> എന്ന് എഴുതിയാൽ, ListItem കംപോണന്റിന് props.key വായിക്കാൻ കഴിയില്ല. റെക്റ്റ് (React) ഇത് അതിന്റെ ആഭ്യന്തരമായ ബുക്ക് കീപ്പിംഗിനായി ഉപയോഗിക്കുന്നു. നിങ്ങളുടെ കംപോണന്റിന് അതിന്റെ ലോജിക്കിനായി ആ ഐഡന്റിഫയർ ആവശ്യമാണെങ്കിൽ, itemId പോലുള്ള മറ്റൊരു പേരിൽ അത് പ്രത്യേകം പാസ്സ് ചെയ്യുക.
സുരക്ഷിതമായിരിക്കാൻ പാലിക്കേണ്ട പ്രായോഗിക നിയമങ്ങൾ
- ഡാറ്റാബേസ് ഐഡികൾ (database IDs) ഉപയോഗിക്കുക. അവ യുണീക് ആണ്, കൂടാതെ നമ്പറുകളോ സ്ട്രിംഗുകളോ അടിസ്ഥാനമാക്കിയുള്ളതും സ്ഥിരതയുള്ളതുമാണ്.
- ക്ലയന്റ്-ഒൺലി (client-only) ഡാറ്റയ്ക്ക്
uuidഅല്ലെങ്കിൽnanoidഉപയോഗിക്കുക. റെക്കോർഡ് ക്രിയേറ്റ് ചെയ്യുമ്പോൾ മാത്രം ഐഡി ജനറേറ്റ് ചെയ്യുക, കംപോണന്റ് റെൻഡറിനുള്ളിൽ അല്ല. - ലിസ്റ്റിൽ മാറ്റങ്ങൾ വരാൻ സാധ്യതയുണ്ടെങ്കിൽ ഒരിക്കലും അറേ ഇൻഡക്സിൽ (array index) നിന്ന് കീ നിർമ്മിക്കരുത്. സോർട്ടിംഗ് (sorting), ഫിൽട്ടറിംഗ് (filtering), ഡിലീറ്റിംഗ് (deleting) എന്നിവ വിഷ്വൽ ബഗുകൾക്കും സ്റ്റേറ്റ് ബഗുകൾക്കും കാരണമാകും.
- ഒരിക്കലും
Math.random(),Date.now(), അല്ലെങ്കിൽ റെൻഡറുകൾക്കിടയിൽ മാറുന്ന ഏതെങ്കിലും മൂല്യങ്ങൾ ഉപയോഗിക്കരുത്. ഇത് അനാവശ്യമായ അൺമൗണ്ടിംഗിനും (unmounting) റീമൗണ്ടിംഗിനും (remounting) കാരണമാകും. - ഫ്രാഗ്മെന്റുകൾ ഒരു
map-നുള്ളിലാണെങ്കിൽ അവയ്ക്ക് കീ ആവശ്യമാണെങ്കിൽ ലോങ്ങ് ഫോം (long form) ഉപയോഗിക്കണം എന്ന് ഓർക്കുക. - കീ എലമെന്റ്
map-ലൂടെ തിരികെ ലഭിക്കുന്നതിൽ നൽകുക, ഒരു ചൈൽഡ് കംപോണന്റിനുള്ളിലല്ല.
പ്രധാന പാഠം
കീ പ്രോപ്പ് (key prop) എന്നത് വെറുമൊരു ഡെക്കറേറ്റീവ് ലിന്റ് റൂൾ (lint rule) അല്ല. റെൻഡറുകൾക്കിടയിൽ ഐഡന്റിറ്റി നിലനിർത്താൻ റെക്റ്റ് ഉപയോഗിക്കുന്നത് ഇതാണ്. ഒരു ഡാറ്റാബേസ് ടേബിളിലെ പ്രൈമറി കീ (primary key) പോലെ ഇതിനെ കരുതുക. ആ ഐഡന്റിറ്റി സ്ഥിരമാണെങ്കിൽ, എലമെന്റുകൾ കൃത്യമായി നീക്കാനും അപ്ഡേറ്റ് ചെയ്യാനും നീക്കം ചെയ്യാനും റെക്റ്റിന് സാധിക്കും. കീ ഇല്ലാതിരിക്കുകയോ അല്ലെങ്കിൽ അസ്ഥിരമായിരിക്കുകയോ ചെയ്താൽ, അത് തെറ്റായ UI സ്റ്റേറ്റിനും (UI state) സ്ലോ ആയ റീകൺസിലിയേഷനും (reconciliation) കാരണമാകും. ഡാറ്റാ ലെയറിൽ (data layer) തന്നെ ഇത് ശരിയാക്കിയാൽ, ലിസ്റ്റുകൾ എത്ര വലുതായാലും അല്ലെങ്കിൽ എത്ര മാറ്റം വന്നാലും അവ കൃത്യമായി പ്രവർത്തിക്കും.
