તમે પેજ રિફ્રેશ કરો છો અને સ્ક્રીન ખાલી રહે છે. અથવા કદાચ બટન પર ક્લિક કરવાથી તમારા CPU ફેનનો અવાજ વધી જાય છે. પછી કન્સોલ (console) માં પેલો ડરામણો વોર્નિંગ મેસેજ આવે છે: Maximum update depth exceeded. React એ બ્રેક મારી દીધી છે કારણ કે તમારું component એક અનંત લૂપ (infinite loop) માં ફસાઈ ગયું છે. તે React એપ્લિકેશન્સમાં સૌથી સામાન્ય ભૂલોમાંની એક છે, અને તે સામાન્ય રીતે તમારો કોડ ખરેખર ક્યારે ચાલે છે તે વિશેની એક સામાન્ય ગેરસમજમાંથી ઉદભવે છે.
તેને સુધારવા માટે, તમારે એ ચોક્કસ ક્ષણ સમજવી પડશે જ્યારે એક render એ re-render બની જાય છે, અને શા માટે state માં થતા ફેરફારો render path ની બહાર હોવા જોઈએ.
React તમારા Component ને કેવી રીતે Render કરે છે
React components માંથી user interfaces બનાવે છે. આધુનિક React માં, તે components functions છે. જ્યારે પણ React ને સ્ક્રીન પર તમારું component બતાવવાની જરૂર પડે છે, ત્યારે તે ફક્ત તે function ને કોલ કરે છે. function ની અંદર, તમે renders વચ્ચે વસ્તુઓ યાદ રાખવા માટે state નો ઉપયોગ કરી શકો છો. State React ને જણાવે છે કે કયો ડેટા component નો છે અને, સૌથી મહત્વનું એ કે, ક્યારે કંઈક બદલાયું છે અને UI ને અપડેટ કરવાની જરૂર છે.
જ્યારે state બદલાય છે, ત્યારે React એક નવું render શેડ્યૂલ કરે છે. Component function ફરીથી ચાલે છે, નવું JSX રિટર્ન કરે છે, અને React તેને અનુરૂપ DOM ને અપડેટ કરે છે. સામાન્ય ઉપયોગમાં, આ ચક્ર નુકસાનકારક નથી. તમે બટન પર ક્લિક કરો છો, એક event handler state ને અપડેટ કરે છે, React એકવાર re-render થાય છે, અને યુઝરને નવો ટેક્સ્ટ અથવા કલર દેખાય છે.
ભૂલ ત્યારે દેખાય છે જ્યારે એક render પોતે જ બીજા state update ને ટ્રિગર કરે છે. તે નવું state update બીજા render ને ટ્રિગર કરે છે, જે બીજા state update ને ટ્રિગર કરે છે. React થોડા ડઝન ચક્ર સુધી આ સહન કરે છે, અને પછી બ્રાઉઝરને સંપૂર્ણપણે ફ્રીઝ થતું બચાવવા માટે maximum depth error ફેંકે છે.
State પર એક ઝડપી નજર
લૂપનું વિશ્લેષણ કરતા પહેલા, યાદ કરો કે useState hook કેવી રીતે કામ કરે છે. તે તમને બરાબર બે વસ્તુઓ આપે છે: વર્તમાન વેલ્યુ (value) ધરાવતું એક variable, અને તે વેલ્યુ બદલવા માટેનું એક function.
const MessageComponent = () => {
const [message, setMessage] = useState('Welcome');
return <h1>{message}</h1>;
};
અહીં, પ્રથમ render પર message એ 'Welcome' છે. જો તમે પાછળથી setMessage('Goodbye') કોલ કરો છો, તો React ફેરફાર નોંધે છે, MessageComponent ને ફરીથી કોલ કરે છે, અને હવે UI "Goodbye" બતાવે છે. બધું બરાબર છે કારણ કે component body માં કંઈપણ setter ને આપમેળે કોલ કરી રહ્યું નથી. લૂપ ત્યારે શરૂ થાય છે જ્યારે કોઈ બાહ્ય event વગર render phase દરમિયાન setter ફાયર થાય છે.
Body માં સીધું SetState કોલ કરવું
અનંત લૂપ (infinite loop) બનાવવાનો સૌથી સીધો રસ્તો એ છે કે component body ની અંદર સીધું જ state setter function કોલ કરવું. કારણ કે component body દરેક render પર એક્ઝિક્યુટ થાય છે, તેથી setter દરેક render પર ફાયર થાય છે. તે નવું state update બીજા render નું કારણ બને છે. આ ચક્ર કાયમ માટે ચાલતું રહે છે.
ભૂલ કંઈક આવી દેખાય છે:
const Counter = () => {
const [count, setCount] = useState(0);
setCount(count + 1);
return <div>{count}</div>;
};
જ્યારે પણ Counter render થાય છે, ત્યારે તે count માં વધારો કરે છે. React નવો નંબર બતાવવા માટે ફરીથી render થાય છે, ફરીથી setCount(count + 1) જુએ છે, અને ફરી એકવાર વધારો કરે છે. તેનો ઉકેલ સરળ છે: render દરમિયાન ક્યારેય તમારા component ના top level પર state setter કોલ કરશો નહીં. State updates એ યુઝરના events અથવા side effects ને પ્રતિસાદ આપવા જોઈએ, સ્ક્રીન પર પેઇન્ટ કરવાની ક્રિયાને નહીં. તેના બદલે તે update ને event handler માં ખસેડો:
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
render દરમિયાન setters કોલ કરવા માટેનો એકમાત્ર અપવાદ એ છે જ્યારે તમે props માંથી નવું state ગણતા હોવ, અને તેમ છતાં તમારે અલગ પેટર્નનો ઉપયોગ કરવો જોઈએ, જેમ કે વેલ્યુને સીધી રીતે મેળવવી (deriving) અથવા જાણીજોઈને useEffect નો ઉપયોગ કરવો.
Reference ને બદલે Function Call પાસ કરવું
બીજું સામાન્ય કારણ JSX માં થતી નાની ભૂલ (typo) છે. જ્યારે તમે event handler જોડો છો, ત્યારે તમારે function પોતે જ પાસ કરવાની જરૂર છે. જો તમે ભૂલથી JSX માં જ function ને invoke કરી દો છો, તો તે render cycle દરમિયાન તરત જ ચાલી જાય છે.
const Toggle = () => {
const [isOn, setIsOn] = useState(false);
const handleToggle = () => setIsOn(!isOn);
return <button onClick={handleToggle()}>Toggle</button>;
};
કૌંસ (parentheses) સાથે handleToggle() લખીને, તમે React ને યુઝર ક્લિક કરે ત્યારે પછીથી કોલ કરવા માટે function નથી આપી રહ્યા. તમે તેને અત્યારે જ કોલ કરી રહ્યા છો જ્યારે React virtual DOM બનાવી રહ્યું છે. કારણ કે handleToggle state ને અપડેટ કરે છે, તેથી component re-render થાય છે. તે re-render દરમિયાન, React ફરીથી handleToggle() જુએ છે અને તેને ફરીથી કોલ કરે છે. આ લૂપ ક્યારેય પૂરી થતી નથી. સાચું વર્ઝન કૌંસ દૂર કરે છે:
return <button onClick={handleToggle}>Toggle</button>;
જો તમારે arguments પાસ કરવાની જરૂર હોય, તો કોલને anonymous function માં લપેટી (wrap) દો:
return <button onClick={() => handleToggle(true)}>Switch On</button>;
આ તફાવત refactoring દરમિયાન અનુભવી ડેવલપર્સને પણ મૂંઝવણમાં મૂકી શકે છે. આ કૌંસ (parentheses) પર નજર રાખતા રહો.
useEffect Dependency Trap
ડેટા ફેચિંગ, browser APIs સાથે સિંક કરવું અથવા મેન્યુઅલી DOM ને મેનીપ્યુલેટ કરવા જેવા side effects માટે Effects એ યોગ્ય જગ્યા છે. પરંતુ useEffect એ React સ્ક્રીન પર render commit કર્યા પછી ચાલે છે. જો તમારો effect state ને અપડેટ કરે છે, તો React re-render થશે. સામાન્ય રીતે તે બરાબર છે. તે લૂપ ત્યારે બને છે જ્યારે effect દરેક render પછી ચાલે છે અને હંમેશા સમાન state ને અપડેટ કરે છે.
આ ખોટી પેટર્ન પર વિચાર કરો:
const UserProfile = () => {
const [user, setUser] = useState({});
useEffect(() => {
setUser({ name: 'Ada', role: 'Admin' });
});
return <div>{user.name}</div>;
};
કારણ કે અહીં કોઈ dependency array નથી, આ effect દરેક render પછી ચાલે છે. તે user ને સેટ કરે છે, જે render ટ્રિગર કરે છે. તે render પછી, effect ફરીથી ચાલે છે અને ફરીથી user ને સેટ કરે છે. React આ ચક્રને ઓળખી લે છે અને error ફેંકે છે.
આનો ઉકેલ એ છે કે યોગ્ય dependency array આપીને React ને જણાવવું કે effect ને ખરેખર ક્યારે ચલાવવાની જરૂર છે. જો effect માત્ર mount વખતે જ એકવાર ચલાવવું હોય, તો ખાલી array પાસ કરો:
useEffect(() => {
setUser({ name: 'Ada', role: 'Admin' });
}, []);
જો effect કોઈ prop અથવા state પર આધારિત હોય, તો array માં ફક્ત તે જ variable નો સમાવેશ કરો. જોકે, સાવધ રહેજો. દરેક render વખતે બદલાતા variable નો સમાવેશ કરવાથી તમે માત્ર અલગ રીતે એ જ લૂપ ફરીથી બનાવશો. ઉદાહરણ તરીકે, જો તમે dependencies માં object literal નો સમાવેશ કરો છો અને તે object દરેક parent render પર ફરીથી બનાવવામાં આવે છે, તો effect અનંતકાળ સુધી ચાલશે. આવા કિસ્સાઓમાં, તમારે object બનાવવાની પ્રક્રિયા component ની બહાર લઈ જવી પડી શકે છે અથવા તેને memoize કરવી પડી શકે છે.
પ્રેક્ટિકલ ડિબગીંગ સ્ટેપ્સ
જ્યારે તમને આ error આવે છે, ત્યારે stack trace ખૂબ જ અટપટું લાગી શકે છે કારણ કે React પહેલેથી જ આ ચક્ર ડઝનબંધ વખત દોહરાવી ચૂક્યું હોય છે. કયો component વારંવાર નામ ધરાવે છે તે શોધવા માટે trace ના ઉપરના ભાગથી વાંચવાનું શરૂ કરો. ત્યારબાદ આ ત્રણ જગ્યાએ state setters શોધો:
- કોઈપણ handler અથવા hook ની બહાર, component ની મુખ્ય body માં.
- JSX event attributes જ્યાં તમે
handlerને બદલેhandler()લખ્યું હોઈ શકે છે. useEffecthooks જેમાં dependency array નથી અથવા જે અસ્થિર (unstable) references પર આધારિત છે.
જ્યાં સુધી error બંધ ન થાય ત્યાં સુધી દરેક state setter ને કામચલાઉ રીતે comment out કરો. તે તમને ચોક્કસ જણાવશે કે કયું update દોષિત છે. જો setter કોઈ effect ની અંદર હોય, તો તમારી જાતને પૂછો કે શું તમને ત્યાં ખરેખર state ની જરૂર છે? ક્યારેક ડેવલપર્સ effect ની અંદર props માંથી local state સેટ કરે છે જ્યારે તેઓ JSX માં સીધા જ prop નો ઉપયોગ કરી શકતા હોય.
મુખ્ય વાત
Maximum update depth error એ કોઈ રહસ્યમય React bug નથી. તે એક safety net છે. તેનો અર્થ એ છે કે તમારું component બાહ્ય સિગ્નલની રાહ જોવાને બદલે પોતાને ફરીથી render કરવાનો પ્રયાસ કરી રહ્યું છે. render ને એવા events તરીકે જોવાની આદત છોડો જે વધુ state ઉત્પન્ન કરવા જોઈએ. render ને કારણો તરીકે નહીં, પણ state ના શુદ્ધ પરિણામો તરીકે ગણો. state updates ને event handlers, callbacks, અથવા સાવચેતીપૂર્વક પસંદ કરેલા dependencies ધરાવતા effects ની અંદર રાખો, અને તમે આ error ફરી ક્યારેય નહીં જુઓ.
