మీరు React లో కొంత సమయం గడిపితే, మీ కన్సోల్‌లో పసుపు రంగు హెచ్చరికను చూసి ఉంటారు: “Each child in a list should have a unique ‘key’ prop.” ఇది కేవలం ఒక సలహా లాగా అనిపించవచ్చు, కానీ మీ లిస్ట్ ఐటమ్స్‌ను (list items) వేరు చేయలేకపోతున్నానని React మిమ్మల్ని హెచ్చరిస్తోంది. దీనిని నిర్లక్ష్యం చేస్తే, మీరు చివరికి ఒక బగ్‌ను (bug) ఎదుర్కోవాల్సి వస్తుంది—స్టేట్ (state) తప్పు రో (row) కి మారడం, టెక్స్ట్ ఇన్‌పుట్‌లు ఫోకస్ కోల్పోవడం లేదా యానిమేషన్లు తప్పు ఎలిమెంట్‌పై ప్లే అవ్వడం వంటి సమస్యలు రావచ్చు.

React యొక్క రెండరింగ్ ఇంజిన్ మీ UIని పిక్సెల్ బై పిక్సెల్ పోల్చదు. ఇది virtual DOM అని పిలవబడే తేలికపాటి ఆబ్జెక్ట్ ట్రీని నిర్మిస్తుంది, కొత్త ట్రీని పాత ట్రీతో పోల్చి చూస్తుంది, మరియు రియల్ DOM కోసం అవసరమైన అతి తక్కువ మార్పులను లెక్కిస్తుంది. మీరు ఒక లిస్ట్‌ను రెండర్ చేసినప్పుడు, React దానిని సిబ్లింగ్ ఎలిమెంట్స్ (sibling elements) యొక్క అరేగా చూస్తుంది. కీలు (keys) లేకపోతే, ఒక ఐటమ్ కదిలిందా, రీప్లేస్ అయిందా లేదా తొలగించబడిందా అని తెలుసుకోవడానికి దానికి నమ్మకమైన మార్గం ఉండదు. అది పొజిషన్ (position) ఆధారంగా మ్యాచ్ చేయడానికి ప్రయత్నిస్తుంది, ఇది చాలా అస్థిరమైన పద్ధతి. కీలు స్థిరమైన గుర్తింపులుగా (stable identities) పనిచేస్తాయి. అవి React కి, “ఈ ఎలిమెంట్ ఇప్పుడు వేరే స్లాట్‌లో ఉన్నప్పటికీ, ఇది మునుపటి ఎలిమెంట్‌తో సమానమైనదే” అని చెబుతాయి. మీరు దీనిని తప్పుగా చేస్తే, ఖచ్చితమైన అప్‌డేట్‌లకు బదులుగా ఊహల మీద ఆధారపడాల్సి వస్తుంది.

The Minimum Fix

ఈ హెచ్చరిక సాధారణంగా map కాల్ లోపల కనిపిస్తుంది. మీరు iterator నుండి తిరిగి వచ్చే టాప్-లెవల్ ఎలిమెంట్‌కు ఒక ప్రత్యేకమైన (unique) విలువను 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 కాంపోనెంట్‌గా విడదీస్తే, కీ మాత్రం కాల్ సైట్ (call site) వద్ద ఆ కాంపోనెంట్‌కే ఉండాలి:

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

UserItem లోపల ఉన్న అంతర్గత <div> కి కీని ఉంచడం వల్ల హెచ్చరిక ఆగదు మరియు reconciliation ప్రవర్తన కూడా సరిదిద్దబడదు. ఇటరేటర్ ద్వారా తిరిగి వచ్చే ఎలిమెంట్‌పైనే React కీ కోసం వెతుకుతుంది.

Why Index as a Key Is Dangerous

map యొక్క రెండవ ఆర్గ్యుమెంట్‌ను ఉపయోగించి ఈ హెచ్చరికను ఆపాలని అనిపించవచ్చు:

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

ఇది కన్సోల్‌లో వచ్చే హెచ్చరికను తగ్గిస్తుంది కానీ, అసలు సమస్యను పరిష్కరించదు. అరే ఇండెక్స్‌లు (Array indices) గుర్తింపులు (identities) కావు. అవి కేవలం స్థానాలు (positions), మరియు స్థానాలు మారుతుంటాయి.

మూరు యూజర్లు ఈ క్రమంలో ఉన్న లిస్ట్‌ను ఊహించుకోండి:

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

మీరు Alice ని డిలీట్ చేస్తే, Bob ఇండెక్స్ 0 కి, Charlie ఇండెక్స్ 1 కి మారుతారు. React కొత్త ట్రీని పాత ట్రీతో పోల్చి చూస్తుంది. ఇండెక్స్ 0 లో ఇప్పుడు Bob డేటా ఉందని అది గమనిస్తుంది, కాబట్టి ఇది మునుపటి Alice డేటాను చూపించిన DOM నోడ్‌ను మారుస్తుంది (mutate చేస్తుంది). ఒకవేళ ఆ నోడ్‌కు ఫోకస్ ఉంటే, కర్సర్ మొదటి రోలోనే ఉంటుంది, కానీ టెక్స్ట్ మాత్రం Bob గా మారుతుంది. ఒకవేళ ఆ రో లో లోకల్ స్టేట్ ఉన్న <input> ఉంటే, ఆ స్టేట్ ఇండెక్స్ 0 కే ఉండిపోతుంది. యూజర్ Bob రో అని అనుకుని టైప్ చేస్తారు, కానీ స్టేట్ మాత్రం Alice కి చెందినదిగా ఉంటుంది. మీరు ఐటమ్స్‌ను సార్ట్ చేసినా, ఫిల్టర్ చేసినా లేదా కొత్తవి చేర్చినా (prepend) ఇదే గందరగోళం జరుగుతుంది. ఇండెక్స్ కీని ఉపయోగించడానికి ఏకైక సురక్షితమైన మార్గం, ఆ లిస్ట్ పూర్తిగా స్టాటిక్ (static) గా ఉన్నప్పుడు మాత్రమే: అంటే ఎటువంటి రీఆర్డరింగ్, ఫిల్టరింగ్, ఇన్సర్టింగ్ లేదా డిలీటింగ్ లేని సందర్భం. ఎప్పటికీ మారని హార్డ్‌కోడెడ్ నావిగేషన్ లింక్స్ దీనికి మంచి ఉదాహరణ. మిగిలిన అన్నింటికీ ఒక నిజమైన ఐడెంటిఫైయర్ అవసరం.

Where to Find a Stable Key

ఉత్తమమైన కీ మీ డేటా మోడల్‌లో ఇప్పటికే ఉన్న ఒక ప్రత్యేకమైన ఐడెంటిఫైయర్ (unique identifier). id వంటి డేటాబేస్ ప్రైమరీ కీలు (primary keys) చాలా ఉత్తమమైనవి, ఎందుకంటే అవి ఖచ్చితంగా యూనిక్ గా ఉంటాయి మరియు రెండర్‌ల అంతటా స్థిరంగా ఉంటాయి. మీ బ్యాకెండ్ uuid, slug లేదా ఇతర సహజమైన యూనిక్ ఫీల్డ్స్‌తో ఆబ్జెక్ట్‌లను అందిస్తే, వాటిని ఉపయోగించండి.

మీ API రెస్పాన్స్ లో ఎటువంటి యూనిక్ ఫీల్డ్ లేనప్పుడు, మీకు రెండు మార్గాలు ఉన్నాయి. మొదటిది, మీ బ్యాకెండ్ టీమ్‌తో మాట్లాడి id ని చేర్చమని అడగండి. ప్రైమరీ కీ లేకుండా రిలేషనల్ డేటాను పంపడం అనేది ఒక తప్పు పద్ధతి (smell), మరియు మూలంలోనే (source) దీనిని సరిదిద్దడం వల్ల మీ స్టాక్ అంతటా స్పష్టత వస్తుంది. రెండవది, మీరు పూర్తిగా క్లయింట్ సైడ్ లో ఐటమ్స్‌ను జనరేట్ చేస్తున్నట్లయితే—ఉదాహరణకు, యూజర్లు సర్వర్‌కు వెళ్లే ముందే టాస్క్‌లను క్రియేట్ చేసే ఒక todo లిస్ట్—క్రియేషన్ సమయంలోనే ఒక ID ని జనరేట్ చేయండి. uuid లేదా nanoid వంటి లైబ్రరీలు సరిగ్గా ఇందుకోసమే రూపొందించబడ్డాయి. యూజర్ ఫారమ్‌ను సబ్మిట్ చేసినప్పుడు ID ని జనరేట్ చేసి, దానిని ఆబ్జెక్ట్‌పై స్టోర్ చేసి, కీ కోసం ఎల్లప్పుడూ దానిని ఉపయోగించండి.

రెండర్ పాత్ (render path) లోపల ఎప్పుడూ కీని జనరేట్ చేయకండి. కాంపోనెంట్ రెండర్ అవుతున్నప్పుడు Math.random() లేదా Date.now() ని పిలవడం వల్ల ప్రతిసారీ కొత్త విలువ వస్తుంది. React కొత్త కీని చూసి, అది కొత్త ఎలిమెంట్ అని భావించి, పాత DOM నోడ్‌ను నాశనం చేసి, కొత్తదాన్ని సృష్టిస్తుంది. ఆ ఎలిమెంట్‌లోని ఏ స్టేట్ అయినా రీసెట్ అవుతుంది. ఫోకస్ కోల్పోతుంది. React అనవసరమైన DOM పనులను చేయడం వల్ల పెర్ఫార్మెన్స్ తగ్గిపోతుంది. కీ లేకపోవడం కంటే, రాండమ్‌గా జనరేట్ చేసిన కీ మరింత ప్రమాదకరం.

Fragments, Components, and Scope

తక్కువగా గమనించబడే ఒక చిక్కు React Fragments కి సంబంధించింది. మీరు డేటాపై మ్యాప్ (map) చేసి, ఒక <div> వంటి కవర్‌ (wrapper) లేకుండా బహుళ సిబ్లింగ్ ఎలిమెంట్లను (sibling elements) రిటర్న్ చేయాలనుకున్నప్పుడు, మీరు ఈ షార్ట్ సింటాక్స్ (short syntax) ఉపయోగించవచ్చు:

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

<>...</> అనే షార్ట్‌హ్యాండ్ (shorthand) ప్రోప్స్‌ను (props) సపోర్ట్ చేయదు, అంటే మీరు దానికి కీ (key)ని జోడించలేరు. ఈ సందర్భంలో, పూర్తి ఎక్స్‌ప్లిసిట్ సింటాక్స్‌కు (explicit syntax) మారండి:

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

రెండర్‌ల (renders) మధ్య ఆ జంటను ఒకే యూనిట్‌గా ట్రాక్ చేయడానికి React కి Fragment పై ఆ కీ అవసరం.

మరొక సూక్ష్మమైన విషయం: కీలు (keys) సాధారణ అర్థంలో ప్రోప్స్ (props) కావు. మీరు <ListItem key={item.id} /> అని రాస్తే, ListItem కాంపోనెంట్ props.keyని చదవలేదు. React దీనిని అంతర్గతంగా బుక్‌కీపింగ్ (bookkeeping) కోసం ఉపయోగిస్తుంది. మీ కాంపోనెంట్‌కు దాని స్వంత లాజిక్ కోసం ఆ ఐడెంటిఫైయర్ (identifier) నిజంగా అవసరమైతే, దానిని itemId వంటి వేరే పేరుతో విడిగా పంపండి.

మిమ్మల్ని సురక్షితంగా ఉంచే ప్రాక్టికల్ రూల్స్ (Practical Rules)

  • డేటాబేస్ IDలను ప్రాధాన్యత ఇవ్వండి. అవి యూనిక్ (unique), సంఖ్యాపరమైనవి లేదా స్ట్రింగ్ ఆధారితమైనవి మరియు స్థిరంగా (stable) ఉంటాయి.
  • క్లయింట్-ఓన్లీ (client-only) డేటా కోసం uuid లేదా nanoid ఉపయోగించండి. రికార్డు సృష్టించబడినప్పుడు ఒకేసారి IDని జనరేట్ చేయండి, కాంపోనెంట్ రెండర్ లోపల కాదు.
  • లిస్ట్ మారే అవకాశం ఉంటే, ఎప్పుడూ అర్రే ఇండెక్స్ (array index) నుండి కీని పొందకండి. సార్టింగ్ (sorting), ఫిల్టరింగ్ (filtering) మరియు డిలీటింగ్ (deleting) వల్ల విజువల్ మరియు స్టేట్ బగ్స్ (visual and state bugs) వచ్చే అవకాశం ఉంది.
  • Math.random(), Date.now() లేదా రెండర్‌ల మధ్య మారే ఏ విలువను కూడా ఎప్పుడూ ఉపయోగించకండి. ఇది అనవసరమైన అన్‌మౌంటింగ్ (unmounting) మరియు రీమౌంటింగ్‌కు (remounting) దారితీస్తుంది.
  • Fragments లోపల map ఉపయోగిస్తున్నప్పుడు మరియు కీ అవసరమైనప్పుడు, అవి లాంగ్ ఫామ్ (long form) లో ఉండాలని గుర్తుంచుకోండి.
  • కీని map ద్వారా రిటర్న్ చేయబడిన ఎలిమెంట్‌పై ఉంచండి, చైల్డ్ కాంపోనెంట్ లోపల కాదు.

అసలైన సారాంశం (The Real Takeaway)

key ప్రోప్ అనేది కేవలం ఒక డెకరేటివ్ లింట్ రూల్ (decorative lint rule) మాత్రమే కాదు. రెండర్‌ల మధ్య React తన గుర్తింపును (identity) ఎలా కాపాడుకుంటుందో ఇది తెలియజేస్తుంది. దీనిని డేటాబేస్ టేబుల్‌లోని ప్రైమరీ కీ (primary key) లాగా భావించండి. ఆ గుర్తింపు స్థిరంగా ఉన్నప్పుడు, React ఎలిమెంట్లను ఖచ్చితంగా తరలించగలదు, అప్‌డేట్ చేయగలదు మరియు తొలగించగలదు. అది లేకపోయినా లేదా అస్థిరంగా ఉన్నా, దెబ్బతిన్న UI స్టేట్ మరియు నెమ్మదైన రీకన్సిలియేషన్ (reconciliation) రూపంలో మీరు నష్టపోతారు. డేటా లేయర్‌లో (data layer) దీనిని ఒకసారి సరిచేయండి, అప్పుడు మీ లిస్ట్‌లు ఎంత పెరిగినా లేదా మారినా అవి ఊహించిన విధంగానే పనిచేస్తాయి.