જો તમે React માં થોડો પણ સમય વિતાવ્યો હોય, તો તમે તમારા કન્સોલમાં પીળી ચેતવણી જોઈ હશે: “Each child in a list should have a unique ‘key’ prop.” તે એક નમ્ર સૂચન જેવું લાગે છે, પરંતુ React ખરેખર તમને ચેતવણી આપી રહ્યું છે કે તે તમારી લિસ્ટની આઇટમ્સ વચ્ચેનો તફાવત પારખી શકતું નથી. તેને અવગણો, અને તમે અંતે એક એવો બગ (bug) પેદા કરશો જેને ફરીથી બનાવવો (reproduce કરવો) ખૂબ જ કંટાળાજનક હશે—જેમ કે સ્ટેટ (state) ખોટી રો (row) પર જવું, ટેક્સ્ટ ઇનપુટ્સનું ફોકસ ગુમાવવું, અથવા ખોટા એલિમેન્ટ પર એનિમેશન ચાલવું.
React નું રેન્ડરિંગ એન્જિન તમારા UI ની પિક્સેલ બાય પિક્સેલ સરખામણી કરતું નથી. તે ઓબ્જેક્ટ્સનું એક હળવું વૃક્ષ બનાવે છે જેને virtual DOM કહેવામાં આવે છે, નવા વૃક્ષની જૂના વૃક્ષ સાથે સરખામણી કરે છે, અને real DOM માટે જરૂરી ફેરફારોનો લઘુત્તમ સેટ ગણે છે. જ્યારે તમે લિસ્ટ રેન્ડર કરો છો, ત્યારે React સબલિંગ (sibling) એલિમેન્ટ્સનો એરે (array) જુએ છે. કી (keys) વગર, તેની પાસે એ જાણવાનો કોઈ વિશ્વસનીય રસ્તો નથી કે કોઈ આઇટમ ખસી ગઈ છે, બદલાઈ ગઈ છે અથવા દૂર કરવામાં આવી છે. તે પોઝિશન (સ્થાન) દ્વારા મેચ કરવાનું ડિફોલ્ટ પસંદ કરે છે, જે અસ્થિર (fragile) છે. કી સ્થિર ઓળખ તરીકે કામ કરે છે. તેઓ React ને કહે છે, "આ એલિમેન્ટ પહેલા જેવું જ છે, ભલે તે હવે અલગ સ્લોટમાં હોય." જો તમે આમાં ભૂલ કરશો, તો તમે ચોક્કસ (deterministic) અપડેટ્સને બદલે અંદાજ લગાવવાની પ્રક્રિયામાં મુકાઈ જશો.
ન્યૂનતમ સુધારો
આ ચેતવણી સામાન્ય રીતે map કોલની અંદર દેખાય છે. તમારે ઇટરેટર (iterator) માંથી રિટર્ન થયેલા ટોપ-લેવલ એલિમેન્ટ પર 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>
);
};
key ને સીધું જ map કોલબેક ની અંદરના એલિમેન્ટને અસાઇન કરવું જોઈએ. જો તમે <li ને અલગ UserItem કમ્પોનન્ટમાં અલગ કરો છો, તો પણ કી કોલ સાઇટ (call site) પર કમ્પોનન્ટ પર જ હોવી જોઈએ:
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
UserItem ની અંદર તેના આંતરિક <div> પર કી રાખવાથી ચેતવણી દૂર થશે નહીં અને reconciliation વર્તણૂક પણ સુધરશે નહીં. React ઇટરેટર દ્વારા રિટર્ન કરવામાં આવેલા એલિમેન્ટ પર કી શોધે છે.
કી તરીકે ઇન્ડેક્સ (Index) વાપરવો શા માટે જોખમી છે
map ના બીજા આર્ગ્યુમેન્ટનો ઉપયોગ કરીને ચેતવણીને શાંત કરવી આકર્ષક લાગે છે:
{users.map((user, index) => (
<li key={index}>{user.name}</li>
))}
આ કન્સોલનો અવાજ (noise) દૂર કરે છે, પરંતુ તે મૂળ સમસ્યાનું નિરાકરણ લાવતું નથી. એરે ઇન્ડેક્સ (Array indices) એ ઓળખ નથી. તે માત્ર સ્થાન (positions) છે, અને સ્થાન બદલાતા રહે છે.
કલ્પના કરો કે ત્રણ યુઝર્સનું લિસ્ટ આ ક્રમમાં રેન્ડર થાય છે:
- Alice (index 0)
- Bob (index 1)
- Charlie (index 2)
જો તમે Alice ને ડિલીટ કરો છો, તો Bob ઇન્ડેક્સ 0 પર અને Charlie ઇન્ડેક્સ 1 પર ખસી જાય છે. React નવા વૃક્ષની જૂના વૃક્ષ સાથે સરખામણી કરે છે. તે જુએ છે કે ઇન્ડેક્સ 0 માં હવે Bob નો ડેટા છે, તેથી તે અસ્તિત્વ ધરાવતા DOM નોડમાં ફેરફાર કરે છે જે અગાઉ Alice દર્શાવતો હતો. જો તે નોડ પર ફોકસ હોય, તો કર્સર પહેલી રો માં જ રહેશે, પરંતુ ટેક્સ્ટ બદલાઈને Bob થઈ જશે. જો તે રો માં લોકલ સ્ટેટ સાથેનું <input> હોય, તો તે સ્ટેટ ઇન્ડેક્સ 0 સાથે જ અટકી રહેશે. યુઝર જે રો Bob ની લાગે છે તેમાં ટાઇપ કરે છે, પરંતુ સ્ટેટ Alice નું હતું. જ્યારે તમે આઇટમ્સને સોર્ટ (sort), ફિલ્ટર (filter) અથવા પ્રીપેન્ડ (prepend) કરો છો ત્યારે પણ આવું જ અંધાધૂંધી સર્જ
એક ઓછો સ્પષ્ટ પડકાર React Fragments સાથે જોડાયેલો છે. જો તમે ડેટા પર map કરો છો અને કોઈ વ્રેપર <div> વગર બહુવિધ sibling એલિમેન્ટ્સ રિટર્ન કરવાની જરૂર હોય, તો તમે ટૂંકી સિન્ટેક્સનો ઉપયોગ કરી શકો છો:
{items.map((item) => (
<>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</>
))}
શોર્ટકન્ડ <>...</> props ને સપોર્ટ કરતું નથી, જેનો અર્થ છે કે તમે key જોડી શકતા નથી. આ કિસ્સામાં, સંપૂર્ણ સ્પષ્ટ (explicit) સિન્ટેક્સનો ઉપયોગ કરો:
{items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
React ને Fragment પર તે key ની જરૂર છે જેથી તે renders દરમિયાન તે જોડીને એક સિંગલ યુનિટ તરીકે ટ્રેક કરી શકે.
બીજો એક સૂક્ષ્મ મુદ્દો: keys સામાન્ય અર્થમાં props નથી. જો તમે <ListItem key={item.id} /> લખો છો, તો ListItem component props.key વાંચી શકતું નથી. React તેને આંતરિક રીતે બુકકીપિંગ માટે વાપરે છે. જો તમારા component ને તેની પોતાની લોજિક માટે ખરેખર આ identifier ની જરૂર હોય, તો તેને itemId જેવા અલગ નામ હેઠળ અલગથી પાસ કરો.
તમને સુરક્ષિત રાખવા માટેના વ્યવહારુ નિયમો
- Database IDs ને પ્રાધાન્ય આપો. તેઓ યુનિક, ન્યુમેરિક અથવા સ્ટ્રિંગ-આધારિત અને સ્ટેબલ હોય છે.
- Client-only ડેટા માટે
uuidઅથવાnanoidનો ઉપયોગ કરો. ID ને રેકોર્ડ બનાવતી વખતે એક જ વાર જનરેટ કરો, component render ની અંદર નહીં. - જો લિસ્ટ બદલાઈ શકે તેમ હોય, તો ક્યારેય array index માંથી key મેળવશો નહીં (Derive કરશો નહીં). Sorting, filtering અને ડિલીટ કરવાથી વિઝ્યુઅલ અને સ્ટેટ બગ્સ (bugs) આવી શકે છે.
- ક્યારેય
Math.random(),Date.now(), અથવા renders વચ્ચે બદલાતી હોય તેવી કોઈ પણ વેલ્યુનો ઉપયોગ કરશો નહીં. આનાથી બિનજરૂરી unmounting અને remounting થાય છે. - યાદ રાખો કે Fragments ને લાંબા સ્વરૂપ (long form) ની જરૂર હોય છે જો તેઓ
mapની અંદર હોય અને તેમને key ની જરૂર હોય. - Key ને
mapદ્વારા રિટર્ન કરવામાં આવેલા એલિમેન્ટ પર મૂકો, child component ની અંદર નહીં.
મુખ્ય સારાંશ
key prop એ માત્ર સજાવટ માટેનો lint rule નથી. તે React દ્વારા renders દરમિયાન ઓળખ (identity) જાળવી રાખવાની રીત છે. તેને ડેટાબેઝ ટેબલમાં primary key જેવું સમજો. જ્યારે તે ઓળખ સ્ટેબલ હોય, ત્યારે React ચોકસાઈથી એલિમેન્ટ્સને ખસેડી, અપડેટ અને દૂર કરી શકે છે. જ્યારે તે ખૂટે અથવા અસ્થિર હોય, ત્યારે તમારે કરપ્ટ UI સ્ટેટ અને ધીમી reconciliation ની કિંમત ચૂકવવી પડે છે. તેને ડેટા લેયર પર એક જ વાર ઠીક કરો, અને તમારા લિસ્ટ ગમે તેટલા વધે કે બદલાય, તે અનુમાનિત રીતે કામ કરશે.
