[{"data":1,"prerenderedAt":6262},["ShallowReactive",2],{"page-\u002Freact\u002F20-performance-optimization":3},{"id":4,"title":5,"body":6,"description":6255,"extension":6256,"meta":6257,"navigation":100,"path":6258,"seo":6259,"stem":6260,"__hash__":6261},"content\u002Freact\u002F20-performance-optimization.md","20 — Performance Optimization",{"type":7,"value":8,"toc":6226},"minimark",[9,13,18,326,331,413,417,791,795,992,996,1340,1344,1606,1610,1897,1901,2342,2346,2646,2650,2960,2964,3123,3127,3245,3249,3568,3572,3873,3877,4831,4835,5129,5133,5318,5322,5467,5471,5656,5660,5671,5944,6077,6081,6223],[10,11,5],"h1",{"id":12},"_20-performance-optimization",[14,15,17],"h2",{"id":16},"measure-first-the-devtools-profiler","Measure First — The DevTools Profiler",[19,20,23],"code-wrapper",{"filename":21,"language":22},"profiler_usage.js","javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F The Profiler tab in React DevTools records every commit (render that\n\u002F\u002F reached the DOM). For each commit it shows:\n\u002F\u002F   - Which components rendered and how long each took\n\u002F\u002F   - WHY each component rendered (state change, parent render, hook dep change)\n\u002F\u002F   - A \"ranked\" view sorting components by render duration\n\u002F\u002F   - A flamechart showing the component tree with render times\n\u002F\u002F\n\u002F\u002F OPTIMIZE BASED ON PROFILER DATA, NOT INTUITION.\n\u002F\u002F The \"why did this render\" panel routinely reveals the actual bottleneck\n\u002F\u002F isn't where you'd guess — optimizing the wrong component wastes effort.\n\n\u002F\u002F Built-in \u003CProfiler> component for programmatic measurement:\nimport { Profiler } from 'react'\n\nfunction App() {\n  return (\n    \u003CProfiler id=\"App\" onRender={(id, phase, actualDuration) => {\n      \u002F\u002F phase: 'mount' | 'update' | 'nested-update'\n      \u002F\u002F actualDuration: ms spent rendering THIS commit (excluding children)\n      if (phase === 'update') {\n        console.log(`${id} update took ${actualDuration}ms`)\n      }\n    }}>\n      \u003CProductPage \u002F>\n    \u003C\u002FProfiler>\n  )\n}\n\n\u002F\u002F In production builds, \u003CProfiler> is a no-op — use it in dev\u002FCI only.\n\u002F\u002F For production profiling, use React's experimental production profiler build.\n","",[30,31,32,41,47,53,59,65,71,77,83,89,95,102,108,126,131,144,153,204,210,216,234,262,268,274,286,297,303,309,314,320],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F The Profiler tab in React DevTools records every commit (render that\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F reached the DOM). For each commit it shows:\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"\u002F\u002F   - Which components rendered and how long each took\n",[33,54,56],{"class":35,"line":55},4,[33,57,58],{"class":39},"\u002F\u002F   - WHY each component rendered (state change, parent render, hook dep change)\n",[33,60,62],{"class":35,"line":61},5,[33,63,64],{"class":39},"\u002F\u002F   - A \"ranked\" view sorting components by render duration\n",[33,66,68],{"class":35,"line":67},6,[33,69,70],{"class":39},"\u002F\u002F   - A flamechart showing the component tree with render times\n",[33,72,74],{"class":35,"line":73},7,[33,75,76],{"class":39},"\u002F\u002F\n",[33,78,80],{"class":35,"line":79},8,[33,81,82],{"class":39},"\u002F\u002F OPTIMIZE BASED ON PROFILER DATA, NOT INTUITION.\n",[33,84,86],{"class":35,"line":85},9,[33,87,88],{"class":39},"\u002F\u002F The \"why did this render\" panel routinely reveals the actual bottleneck\n",[33,90,92],{"class":35,"line":91},10,[33,93,94],{"class":39},"\u002F\u002F isn't where you'd guess — optimizing the wrong component wastes effort.\n",[33,96,98],{"class":35,"line":97},11,[33,99,101],{"emptyLinePlaceholder":100},true,"\n",[33,103,105],{"class":35,"line":104},12,[33,106,107],{"class":39},"\u002F\u002F Built-in \u003CProfiler> component for programmatic measurement:\n",[33,109,111,115,119,122],{"class":35,"line":110},13,[33,112,114],{"class":113},"svdQ7","import",[33,116,118],{"class":117},"ssxIu"," { Profiler } ",[33,120,121],{"class":113},"from",[33,123,125],{"class":124},"sJ6F3"," 'react'\n",[33,127,129],{"class":35,"line":128},14,[33,130,101],{"emptyLinePlaceholder":100},[33,132,134,137,141],{"class":35,"line":133},15,[33,135,136],{"class":113},"function",[33,138,140],{"class":139},"sIsaT"," App",[33,142,143],{"class":117},"() {\n",[33,145,147,150],{"class":35,"line":146},16,[33,148,149],{"class":113},"  return",[33,151,152],{"class":117}," (\n",[33,154,156,159,163,166,169,172,175,177,180,184,187,190,192,195,198,201],{"class":35,"line":155},17,[33,157,158],{"class":117},"    \u003C",[33,160,162],{"class":161},"snvgF","Profiler",[33,164,165],{"class":139}," id",[33,167,168],{"class":113},"=",[33,170,171],{"class":124},"\"App\"",[33,173,174],{"class":139}," onRender",[33,176,168],{"class":113},[33,178,179],{"class":117},"{(",[33,181,183],{"class":182},"sCrzJ","id",[33,185,186],{"class":117},", ",[33,188,189],{"class":182},"phase",[33,191,186],{"class":117},[33,193,194],{"class":182},"actualDuration",[33,196,197],{"class":117},") ",[33,199,200],{"class":113},"=>",[33,202,203],{"class":117}," {\n",[33,205,207],{"class":35,"line":206},18,[33,208,209],{"class":39},"      \u002F\u002F phase: 'mount' | 'update' | 'nested-update'\n",[33,211,213],{"class":35,"line":212},19,[33,214,215],{"class":39},"      \u002F\u002F actualDuration: ms spent rendering THIS commit (excluding children)\n",[33,217,219,222,225,228,231],{"class":35,"line":218},20,[33,220,221],{"class":113},"      if",[33,223,224],{"class":117}," (phase ",[33,226,227],{"class":113},"===",[33,229,230],{"class":124}," 'update'",[33,232,233],{"class":117},") {\n",[33,235,237,240,243,246,249,251,254,256,259],{"class":35,"line":236},21,[33,238,239],{"class":117},"        console.",[33,241,242],{"class":139},"log",[33,244,245],{"class":117},"(",[33,247,248],{"class":124},"`${",[33,250,183],{"class":117},[33,252,253],{"class":124},"} update took ${",[33,255,194],{"class":117},[33,257,258],{"class":124},"}ms`",[33,260,261],{"class":117},")\n",[33,263,265],{"class":35,"line":264},22,[33,266,267],{"class":117},"      }\n",[33,269,271],{"class":35,"line":270},23,[33,272,273],{"class":117},"    }}>\n",[33,275,277,280,283],{"class":35,"line":276},24,[33,278,279],{"class":117},"      \u003C",[33,281,282],{"class":161},"ProductPage",[33,284,285],{"class":117}," \u002F>\n",[33,287,289,292,294],{"class":35,"line":288},25,[33,290,291],{"class":117},"    \u003C\u002F",[33,293,162],{"class":161},[33,295,296],{"class":117},">\n",[33,298,300],{"class":35,"line":299},26,[33,301,302],{"class":117},"  )\n",[33,304,306],{"class":35,"line":305},27,[33,307,308],{"class":117},"}\n",[33,310,312],{"class":35,"line":311},28,[33,313,101],{"emptyLinePlaceholder":100},[33,315,317],{"class":35,"line":316},29,[33,318,319],{"class":39},"\u002F\u002F In production builds, \u003CProfiler> is a no-op — use it in dev\u002FCI only.\n",[33,321,323],{"class":35,"line":322},30,[33,324,325],{"class":39},"\u002F\u002F For production profiling, use React's experimental production profiler build.\n",[327,328,330],"h3",{"id":329},"reading-the-profiler-what-to-look-for","Reading the Profiler — What to Look For",[19,332,334],{"filename":333,"language":22},"profiler_reading.js",[24,335,337],{"className":26,"code":336,"language":22,"meta":28,"style":28},"\u002F\u002F PROFILER WORKFLOW:\n\u002F\u002F 1. Record a specific interaction (type in search, scroll a list, toggle a tab)\n\u002F\u002F 2. Look at the \"ranked\" chart — which component took the most time?\n\u002F\u002F 3. Click it -> \"Why did this render?\" panel shows the cause:\n\u002F\u002F    - \"This component rendered because parent X rendered\"\n\u002F\u002F    - \"hook H changed: state Y\" or \"prop Z changed\"\n\u002F\u002F 4. If the cause is \"parent rendered\" and props didn't change -> React.memo\n\u002F\u002F 5. If the cause is \"prop Z changed\" and Z is an object\u002Farray\u002Ffunction created\n\u002F\u002F    inline -> useMemo\u002FuseCallback to stabilize it\n\u002F\u002F 6. If a single component is genuinely slow -> optimize that component's\n\u002F\u002F    internal logic (useMemo for expensive computation, or split it)\n\n\u002F\u002F ANTI-PATTERN: profiling once, seeing \"everything renders,\" and wrapping\n\u002F\u002F every component in React.memo \"just in case.\" The shallow comparison\n\u002F\u002F itself has overhead — wrapping cheap components adds net cost, not savings.\n",[30,338,339,344,349,354,359,364,369,374,379,384,389,394,398,403,408],{"__ignoreMap":28},[33,340,341],{"class":35,"line":36},[33,342,343],{"class":39},"\u002F\u002F PROFILER WORKFLOW:\n",[33,345,346],{"class":35,"line":43},[33,347,348],{"class":39},"\u002F\u002F 1. Record a specific interaction (type in search, scroll a list, toggle a tab)\n",[33,350,351],{"class":35,"line":49},[33,352,353],{"class":39},"\u002F\u002F 2. Look at the \"ranked\" chart — which component took the most time?\n",[33,355,356],{"class":35,"line":55},[33,357,358],{"class":39},"\u002F\u002F 3. Click it -> \"Why did this render?\" panel shows the cause:\n",[33,360,361],{"class":35,"line":61},[33,362,363],{"class":39},"\u002F\u002F    - \"This component rendered because parent X rendered\"\n",[33,365,366],{"class":35,"line":67},[33,367,368],{"class":39},"\u002F\u002F    - \"hook H changed: state Y\" or \"prop Z changed\"\n",[33,370,371],{"class":35,"line":73},[33,372,373],{"class":39},"\u002F\u002F 4. If the cause is \"parent rendered\" and props didn't change -> React.memo\n",[33,375,376],{"class":35,"line":79},[33,377,378],{"class":39},"\u002F\u002F 5. If the cause is \"prop Z changed\" and Z is an object\u002Farray\u002Ffunction created\n",[33,380,381],{"class":35,"line":85},[33,382,383],{"class":39},"\u002F\u002F    inline -> useMemo\u002FuseCallback to stabilize it\n",[33,385,386],{"class":35,"line":91},[33,387,388],{"class":39},"\u002F\u002F 6. If a single component is genuinely slow -> optimize that component's\n",[33,390,391],{"class":35,"line":97},[33,392,393],{"class":39},"\u002F\u002F    internal logic (useMemo for expensive computation, or split it)\n",[33,395,396],{"class":35,"line":104},[33,397,101],{"emptyLinePlaceholder":100},[33,399,400],{"class":35,"line":110},[33,401,402],{"class":39},"\u002F\u002F ANTI-PATTERN: profiling once, seeing \"everything renders,\" and wrapping\n",[33,404,405],{"class":35,"line":128},[33,406,407],{"class":39},"\u002F\u002F every component in React.memo \"just in case.\" The shallow comparison\n",[33,409,410],{"class":35,"line":133},[33,411,412],{"class":39},"\u002F\u002F itself has overhead — wrapping cheap components adds net cost, not savings.\n",[14,414,416],{"id":415},"reactmemo-skipping-re-renders-for-unchanged-props","React.memo — Skipping Re-renders for Unchanged Props",[19,418,420],{"filename":419,"language":22},"react_memo.js",[24,421,423],{"className":26,"code":422,"language":22,"meta":28,"style":28},"import { memo, useCallback, useState } from 'react'\n\n\u002F\u002F React.memo wraps a component so React SKIPS re-rendering it when its\n\u002F\u002F props are shallow-equal to the previous render's props.\n\u002F\u002F By default, EVERY child re-renders when a parent re-renders — memo opts out.\n\nconst ProductRow = memo(function ProductRow({ product, onSelect }) {\n  console.log('Rendering', product.name)  \u002F\u002F only logs when props actually change\n  return (\n    \u003Ctr onClick={() => onSelect(product.id)}>\n      \u003Ctd>{product.name}\u003C\u002Ftd>\n      \u003Ctd>${product.price}\u003C\u002Ftd>\n    \u003C\u002Ftr>\n  )\n})\n\n\u002F\u002F CRITICAL: memo is useless without stable props from the parent.\n\u002F\u002F A new function\u002Fobject reference every render -> shallow-equal fails -> re-renders.\nfunction ProductTable({ products }) {\n  const [selectedId, setSelectedId] = useState(null)\n\n  \u002F\u002F useCallback stabilizes the function reference across renders.\n  \u002F\u002F Without this, handleSelect is a NEW function every render ->\n  \u002F\u002F ProductRow's memo sees a \"changed\" prop -> re-renders every row.\n  const handleSelect = useCallback((id) => setSelectedId(id), [])\n\n  return (\n    \u003Ctable>\n      \u003Ctbody>\n        {products.map(p => \u003CProductRow key={p.id} product={p} onSelect={handleSelect} \u002F>)}\n      \u003C\u002Ftbody>\n    \u003C\u002Ftable>\n  )\n}\n",[30,424,425,436,440,445,450,455,459,493,511,517,541,555,568,576,580,585,589,594,599,613,644,648,653,658,663,690,694,700,709,718,762,772,781,786],{"__ignoreMap":28},[33,426,427,429,432,434],{"class":35,"line":36},[33,428,114],{"class":113},[33,430,431],{"class":117}," { memo, useCallback, useState } ",[33,433,121],{"class":113},[33,435,125],{"class":124},[33,437,438],{"class":35,"line":43},[33,439,101],{"emptyLinePlaceholder":100},[33,441,442],{"class":35,"line":49},[33,443,444],{"class":39},"\u002F\u002F React.memo wraps a component so React SKIPS re-rendering it when its\n",[33,446,447],{"class":35,"line":55},[33,448,449],{"class":39},"\u002F\u002F props are shallow-equal to the previous render's props.\n",[33,451,452],{"class":35,"line":61},[33,453,454],{"class":39},"\u002F\u002F By default, EVERY child re-renders when a parent re-renders — memo opts out.\n",[33,456,457],{"class":35,"line":67},[33,458,101],{"emptyLinePlaceholder":100},[33,460,461,464,467,470,473,475,477,479,482,485,487,490],{"class":35,"line":73},[33,462,463],{"class":113},"const",[33,465,466],{"class":161}," ProductRow",[33,468,469],{"class":113}," =",[33,471,472],{"class":139}," memo",[33,474,245],{"class":117},[33,476,136],{"class":113},[33,478,466],{"class":139},[33,480,481],{"class":117},"({ ",[33,483,484],{"class":182},"product",[33,486,186],{"class":117},[33,488,489],{"class":182},"onSelect",[33,491,492],{"class":117}," }) {\n",[33,494,495,498,500,502,505,508],{"class":35,"line":79},[33,496,497],{"class":117},"  console.",[33,499,242],{"class":139},[33,501,245],{"class":117},[33,503,504],{"class":124},"'Rendering'",[33,506,507],{"class":117},", product.name)  ",[33,509,510],{"class":39},"\u002F\u002F only logs when props actually change\n",[33,512,513,515],{"class":35,"line":85},[33,514,149],{"class":113},[33,516,152],{"class":117},[33,518,519,521,525,528,530,533,535,538],{"class":35,"line":91},[33,520,158],{"class":117},[33,522,524],{"class":523},"sk71V","tr",[33,526,527],{"class":139}," onClick",[33,529,168],{"class":113},[33,531,532],{"class":117},"{() ",[33,534,200],{"class":113},[33,536,537],{"class":139}," onSelect",[33,539,540],{"class":117},"(product.id)}>\n",[33,542,543,545,548,551,553],{"class":35,"line":97},[33,544,279],{"class":117},[33,546,547],{"class":523},"td",[33,549,550],{"class":117},">{product.name}\u003C\u002F",[33,552,547],{"class":523},[33,554,296],{"class":117},[33,556,557,559,561,564,566],{"class":35,"line":104},[33,558,279],{"class":117},[33,560,547],{"class":523},[33,562,563],{"class":117},">${product.price}\u003C\u002F",[33,565,547],{"class":523},[33,567,296],{"class":117},[33,569,570,572,574],{"class":35,"line":110},[33,571,291],{"class":117},[33,573,524],{"class":523},[33,575,296],{"class":117},[33,577,578],{"class":35,"line":128},[33,579,302],{"class":117},[33,581,582],{"class":35,"line":133},[33,583,584],{"class":117},"})\n",[33,586,587],{"class":35,"line":146},[33,588,101],{"emptyLinePlaceholder":100},[33,590,591],{"class":35,"line":155},[33,592,593],{"class":39},"\u002F\u002F CRITICAL: memo is useless without stable props from the parent.\n",[33,595,596],{"class":35,"line":206},[33,597,598],{"class":39},"\u002F\u002F A new function\u002Fobject reference every render -> shallow-equal fails -> re-renders.\n",[33,600,601,603,606,608,611],{"class":35,"line":212},[33,602,136],{"class":113},[33,604,605],{"class":139}," ProductTable",[33,607,481],{"class":117},[33,609,610],{"class":182},"products",[33,612,492],{"class":117},[33,614,615,618,621,624,626,629,632,634,637,639,642],{"class":35,"line":218},[33,616,617],{"class":113},"  const",[33,619,620],{"class":117}," [",[33,622,623],{"class":161},"selectedId",[33,625,186],{"class":117},[33,627,628],{"class":161},"setSelectedId",[33,630,631],{"class":117},"] ",[33,633,168],{"class":113},[33,635,636],{"class":139}," useState",[33,638,245],{"class":117},[33,640,641],{"class":161},"null",[33,643,261],{"class":117},[33,645,646],{"class":35,"line":236},[33,647,101],{"emptyLinePlaceholder":100},[33,649,650],{"class":35,"line":264},[33,651,652],{"class":39},"  \u002F\u002F useCallback stabilizes the function reference across renders.\n",[33,654,655],{"class":35,"line":270},[33,656,657],{"class":39},"  \u002F\u002F Without this, handleSelect is a NEW function every render ->\n",[33,659,660],{"class":35,"line":276},[33,661,662],{"class":39},"  \u002F\u002F ProductRow's memo sees a \"changed\" prop -> re-renders every row.\n",[33,664,665,667,670,672,675,678,680,682,684,687],{"class":35,"line":288},[33,666,617],{"class":113},[33,668,669],{"class":161}," handleSelect",[33,671,469],{"class":113},[33,673,674],{"class":139}," useCallback",[33,676,677],{"class":117},"((",[33,679,183],{"class":182},[33,681,197],{"class":117},[33,683,200],{"class":113},[33,685,686],{"class":139}," setSelectedId",[33,688,689],{"class":117},"(id), [])\n",[33,691,692],{"class":35,"line":299},[33,693,101],{"emptyLinePlaceholder":100},[33,695,696,698],{"class":35,"line":305},[33,697,149],{"class":113},[33,699,152],{"class":117},[33,701,702,704,707],{"class":35,"line":311},[33,703,158],{"class":117},[33,705,706],{"class":523},"table",[33,708,296],{"class":117},[33,710,711,713,716],{"class":35,"line":316},[33,712,279],{"class":117},[33,714,715],{"class":523},"tbody",[33,717,296],{"class":117},[33,719,720,723,726,728,731,734,737,740,743,745,748,750,752,755,757,759],{"class":35,"line":322},[33,721,722],{"class":117},"        {products.",[33,724,725],{"class":139},"map",[33,727,245],{"class":117},[33,729,730],{"class":182},"p",[33,732,733],{"class":113}," =>",[33,735,736],{"class":117}," \u003C",[33,738,739],{"class":161},"ProductRow",[33,741,742],{"class":139}," key",[33,744,168],{"class":113},[33,746,747],{"class":117},"{p.id} ",[33,749,484],{"class":139},[33,751,168],{"class":113},[33,753,754],{"class":117},"{p} ",[33,756,489],{"class":139},[33,758,168],{"class":113},[33,760,761],{"class":117},"{handleSelect} \u002F>)}\n",[33,763,765,768,770],{"class":35,"line":764},31,[33,766,767],{"class":117},"      \u003C\u002F",[33,769,715],{"class":523},[33,771,296],{"class":117},[33,773,775,777,779],{"class":35,"line":774},32,[33,776,291],{"class":117},[33,778,706],{"class":523},[33,780,296],{"class":117},[33,782,784],{"class":35,"line":783},33,[33,785,302],{"class":117},[33,787,789],{"class":35,"line":788},34,[33,790,308],{"class":117},[327,792,794],{"id":793},"reactmemo-with-custom-comparison","React.memo with Custom Comparison",[19,796,798],{"filename":797,"language":22},"memo_custom_compare.js",[24,799,801],{"className":26,"code":800,"language":22,"meta":28,"style":28},"\u002F\u002F Default: shallow comparison (===) on each prop.\n\u002F\u002F For deep equality, pass a custom comparator as the second argument.\n\u002F\u002F CAUTION: a deep comparison itself has cost — only use when shallow fails\n\u002F\u002F and the component is expensive enough to justify it.\n\nconst ProductCard = memo(function ProductCard({ product, isSelected }) {\n  return \u003Cdiv className={isSelected ? 'selected' : ''}>{product.name}\u003C\u002Fdiv>\n}, (prevProps, nextProps) => {\n  \u002F\u002F Return true = props are \"equal\" -> SKIP re-render\n  \u002F\u002F Return false = props \"changed\" -> re-render\n  return prevProps.isSelected === nextProps.isSelected &&\n         prevProps.product.id === nextProps.product.id &&\n         prevProps.product.name === nextProps.product.name &&\n         prevProps.product.price === nextProps.product.price\n})\n\n\u002F\u002F ANTI-PATTERN: using a deep equality library (lodash.isEqual) as the\n\u002F\u002F comparator for every memoized component. The comparison cost can exceed\n\u002F\u002F the render cost it's meant to skip. Profile before reaching for deep compare.\n",[30,802,803,808,813,818,823,827,855,891,910,915,920,935,947,959,969,973,977,982,987],{"__ignoreMap":28},[33,804,805],{"class":35,"line":36},[33,806,807],{"class":39},"\u002F\u002F Default: shallow comparison (===) on each prop.\n",[33,809,810],{"class":35,"line":43},[33,811,812],{"class":39},"\u002F\u002F For deep equality, pass a custom comparator as the second argument.\n",[33,814,815],{"class":35,"line":49},[33,816,817],{"class":39},"\u002F\u002F CAUTION: a deep comparison itself has cost — only use when shallow fails\n",[33,819,820],{"class":35,"line":55},[33,821,822],{"class":39},"\u002F\u002F and the component is expensive enough to justify it.\n",[33,824,825],{"class":35,"line":61},[33,826,101],{"emptyLinePlaceholder":100},[33,828,829,831,834,836,838,840,842,844,846,848,850,853],{"class":35,"line":67},[33,830,463],{"class":113},[33,832,833],{"class":161}," ProductCard",[33,835,469],{"class":113},[33,837,472],{"class":139},[33,839,245],{"class":117},[33,841,136],{"class":113},[33,843,833],{"class":139},[33,845,481],{"class":117},[33,847,484],{"class":182},[33,849,186],{"class":117},[33,851,852],{"class":182},"isSelected",[33,854,492],{"class":117},[33,856,857,859,861,864,867,869,872,875,878,881,884,887,889],{"class":35,"line":73},[33,858,149],{"class":113},[33,860,736],{"class":117},[33,862,863],{"class":523},"div",[33,865,866],{"class":139}," className",[33,868,168],{"class":113},[33,870,871],{"class":117},"{isSelected ",[33,873,874],{"class":113},"?",[33,876,877],{"class":124}," 'selected'",[33,879,880],{"class":113}," :",[33,882,883],{"class":124}," ''",[33,885,886],{"class":117},"}>{product.name}\u003C\u002F",[33,888,863],{"class":523},[33,890,296],{"class":117},[33,892,893,896,899,901,904,906,908],{"class":35,"line":79},[33,894,895],{"class":117},"}, (",[33,897,898],{"class":182},"prevProps",[33,900,186],{"class":117},[33,902,903],{"class":182},"nextProps",[33,905,197],{"class":117},[33,907,200],{"class":113},[33,909,203],{"class":117},[33,911,912],{"class":35,"line":85},[33,913,914],{"class":39},"  \u002F\u002F Return true = props are \"equal\" -> SKIP re-render\n",[33,916,917],{"class":35,"line":91},[33,918,919],{"class":39},"  \u002F\u002F Return false = props \"changed\" -> re-render\n",[33,921,922,924,927,929,932],{"class":35,"line":97},[33,923,149],{"class":113},[33,925,926],{"class":117}," prevProps.isSelected ",[33,928,227],{"class":113},[33,930,931],{"class":117}," nextProps.isSelected ",[33,933,934],{"class":113},"&&\n",[33,936,937,940,942,945],{"class":35,"line":104},[33,938,939],{"class":117},"         prevProps.product.id ",[33,941,227],{"class":113},[33,943,944],{"class":117}," nextProps.product.id ",[33,946,934],{"class":113},[33,948,949,952,954,957],{"class":35,"line":110},[33,950,951],{"class":117},"         prevProps.product.name ",[33,953,227],{"class":113},[33,955,956],{"class":117}," nextProps.product.name ",[33,958,934],{"class":113},[33,960,961,964,966],{"class":35,"line":128},[33,962,963],{"class":117},"         prevProps.product.price ",[33,965,227],{"class":113},[33,967,968],{"class":117}," nextProps.product.price\n",[33,970,971],{"class":35,"line":133},[33,972,584],{"class":117},[33,974,975],{"class":35,"line":146},[33,976,101],{"emptyLinePlaceholder":100},[33,978,979],{"class":35,"line":155},[33,980,981],{"class":39},"\u002F\u002F ANTI-PATTERN: using a deep equality library (lodash.isEqual) as the\n",[33,983,984],{"class":35,"line":206},[33,985,986],{"class":39},"\u002F\u002F comparator for every memoized component. The comparison cost can exceed\n",[33,988,989],{"class":35,"line":212},[33,990,991],{"class":39},"\u002F\u002F the render cost it's meant to skip. Profile before reaching for deep compare.\n",[14,993,995],{"id":994},"usememo-and-usecallback-refer-to-ch-9","useMemo and useCallback — Refer to Ch 9",[19,997,999],{"filename":998,"language":22},"memo_use_callback.js",[24,1000,1002],{"className":26,"code":1001,"language":22,"meta":28,"style":28},"\u002F\u002F Ch 9 covered useMemo\u002FuseCallback for referential stability.\n\u002F\u002F Here's the performance-specific application:\n\n\u002F\u002F useMemo for EXPENSIVE COMPUTATION (not just referential stability):\nfunction ProductAnalytics({ orders }) {\n  \u002F\u002F Without useMemo: this reduce runs on EVERY render, even if orders\n  \u002F\u002F hasn't changed, because a parent's re-render triggers this component.\n  const revenueByMonth = useMemo(() => {\n    return orders.reduce((acc, order) => {\n      const month = order.date.slice(0, 7)\n      acc[month] = (acc[month] || 0) + order.total\n      return acc\n    }, {})\n  }, [orders])  \u002F\u002F only recomputes when orders reference changes\n\n  return \u003CRevenueChart data={revenueByMonth} \u002F>\n}\n\n\u002F\u002F useCallback for stable function references fed to memoized children:\nfunction Parent() {\n  const [count, setCount] = useState(0)\n  \u002F\u002F Without useCallback: handleClick is a new function every render ->\n  \u002F\u002F any memoized child receiving it re-renders regardless of memo.\n  const handleClick = useCallback(() => {\n    setCount(c => c + 1)\n  }, [])  \u002F\u002F empty deps -> stable forever (setCount is guaranteed stable)\n\n  return \u003CMemoizedChild onClick={handleClick} \u002F>\n}\n\n\u002F\u002F ANTI-PATTERN: wrapping trivial computations in useMemo.\n\u002F\u002F useMemo itself has overhead (storing deps, comparing them every render).\n\u002F\u002F const sum = useMemo(() => a + b, [a, b]) — slower than const sum = a + b.\n\u002F\u002F Reserve useMemo for computations CONFIRMED expensive via profiling.\n",[30,1003,1004,1009,1014,1018,1023,1037,1042,1047,1066,1093,1121,1145,1153,1158,1166,1170,1187,1191,1195,1200,1209,1235,1240,1245,1262,1284,1292,1296,1312,1316,1320,1325,1330,1335],{"__ignoreMap":28},[33,1005,1006],{"class":35,"line":36},[33,1007,1008],{"class":39},"\u002F\u002F Ch 9 covered useMemo\u002FuseCallback for referential stability.\n",[33,1010,1011],{"class":35,"line":43},[33,1012,1013],{"class":39},"\u002F\u002F Here's the performance-specific application:\n",[33,1015,1016],{"class":35,"line":49},[33,1017,101],{"emptyLinePlaceholder":100},[33,1019,1020],{"class":35,"line":55},[33,1021,1022],{"class":39},"\u002F\u002F useMemo for EXPENSIVE COMPUTATION (not just referential stability):\n",[33,1024,1025,1027,1030,1032,1035],{"class":35,"line":61},[33,1026,136],{"class":113},[33,1028,1029],{"class":139}," ProductAnalytics",[33,1031,481],{"class":117},[33,1033,1034],{"class":182},"orders",[33,1036,492],{"class":117},[33,1038,1039],{"class":35,"line":67},[33,1040,1041],{"class":39},"  \u002F\u002F Without useMemo: this reduce runs on EVERY render, even if orders\n",[33,1043,1044],{"class":35,"line":73},[33,1045,1046],{"class":39},"  \u002F\u002F hasn't changed, because a parent's re-render triggers this component.\n",[33,1048,1049,1051,1054,1056,1059,1062,1064],{"class":35,"line":79},[33,1050,617],{"class":113},[33,1052,1053],{"class":161}," revenueByMonth",[33,1055,469],{"class":113},[33,1057,1058],{"class":139}," useMemo",[33,1060,1061],{"class":117},"(() ",[33,1063,200],{"class":113},[33,1065,203],{"class":117},[33,1067,1068,1071,1074,1077,1079,1082,1084,1087,1089,1091],{"class":35,"line":85},[33,1069,1070],{"class":113},"    return",[33,1072,1073],{"class":117}," orders.",[33,1075,1076],{"class":139},"reduce",[33,1078,677],{"class":117},[33,1080,1081],{"class":182},"acc",[33,1083,186],{"class":117},[33,1085,1086],{"class":182},"order",[33,1088,197],{"class":117},[33,1090,200],{"class":113},[33,1092,203],{"class":117},[33,1094,1095,1098,1101,1103,1106,1109,1111,1114,1116,1119],{"class":35,"line":91},[33,1096,1097],{"class":113},"      const",[33,1099,1100],{"class":161}," month",[33,1102,469],{"class":113},[33,1104,1105],{"class":117}," order.date.",[33,1107,1108],{"class":139},"slice",[33,1110,245],{"class":117},[33,1112,1113],{"class":161},"0",[33,1115,186],{"class":117},[33,1117,1118],{"class":161},"7",[33,1120,261],{"class":117},[33,1122,1123,1126,1128,1131,1134,1137,1139,1142],{"class":35,"line":97},[33,1124,1125],{"class":117},"      acc[month] ",[33,1127,168],{"class":113},[33,1129,1130],{"class":117}," (acc[month] ",[33,1132,1133],{"class":113},"||",[33,1135,1136],{"class":161}," 0",[33,1138,197],{"class":117},[33,1140,1141],{"class":113},"+",[33,1143,1144],{"class":117}," order.total\n",[33,1146,1147,1150],{"class":35,"line":104},[33,1148,1149],{"class":113},"      return",[33,1151,1152],{"class":117}," acc\n",[33,1154,1155],{"class":35,"line":110},[33,1156,1157],{"class":117},"    }, {})\n",[33,1159,1160,1163],{"class":35,"line":128},[33,1161,1162],{"class":117},"  }, [orders])  ",[33,1164,1165],{"class":39},"\u002F\u002F only recomputes when orders reference changes\n",[33,1167,1168],{"class":35,"line":133},[33,1169,101],{"emptyLinePlaceholder":100},[33,1171,1172,1174,1176,1179,1182,1184],{"class":35,"line":146},[33,1173,149],{"class":113},[33,1175,736],{"class":117},[33,1177,1178],{"class":161},"RevenueChart",[33,1180,1181],{"class":139}," data",[33,1183,168],{"class":113},[33,1185,1186],{"class":117},"{revenueByMonth} \u002F>\n",[33,1188,1189],{"class":35,"line":155},[33,1190,308],{"class":117},[33,1192,1193],{"class":35,"line":206},[33,1194,101],{"emptyLinePlaceholder":100},[33,1196,1197],{"class":35,"line":212},[33,1198,1199],{"class":39},"\u002F\u002F useCallback for stable function references fed to memoized children:\n",[33,1201,1202,1204,1207],{"class":35,"line":218},[33,1203,136],{"class":113},[33,1205,1206],{"class":139}," Parent",[33,1208,143],{"class":117},[33,1210,1211,1213,1215,1218,1220,1223,1225,1227,1229,1231,1233],{"class":35,"line":236},[33,1212,617],{"class":113},[33,1214,620],{"class":117},[33,1216,1217],{"class":161},"count",[33,1219,186],{"class":117},[33,1221,1222],{"class":161},"setCount",[33,1224,631],{"class":117},[33,1226,168],{"class":113},[33,1228,636],{"class":139},[33,1230,245],{"class":117},[33,1232,1113],{"class":161},[33,1234,261],{"class":117},[33,1236,1237],{"class":35,"line":264},[33,1238,1239],{"class":39},"  \u002F\u002F Without useCallback: handleClick is a new function every render ->\n",[33,1241,1242],{"class":35,"line":270},[33,1243,1244],{"class":39},"  \u002F\u002F any memoized child receiving it re-renders regardless of memo.\n",[33,1246,1247,1249,1252,1254,1256,1258,1260],{"class":35,"line":276},[33,1248,617],{"class":113},[33,1250,1251],{"class":161}," handleClick",[33,1253,469],{"class":113},[33,1255,674],{"class":139},[33,1257,1061],{"class":117},[33,1259,200],{"class":113},[33,1261,203],{"class":117},[33,1263,1264,1267,1269,1272,1274,1277,1279,1282],{"class":35,"line":288},[33,1265,1266],{"class":139},"    setCount",[33,1268,245],{"class":117},[33,1270,1271],{"class":182},"c",[33,1273,733],{"class":113},[33,1275,1276],{"class":117}," c ",[33,1278,1141],{"class":113},[33,1280,1281],{"class":161}," 1",[33,1283,261],{"class":117},[33,1285,1286,1289],{"class":35,"line":299},[33,1287,1288],{"class":117},"  }, [])  ",[33,1290,1291],{"class":39},"\u002F\u002F empty deps -> stable forever (setCount is guaranteed stable)\n",[33,1293,1294],{"class":35,"line":305},[33,1295,101],{"emptyLinePlaceholder":100},[33,1297,1298,1300,1302,1305,1307,1309],{"class":35,"line":311},[33,1299,149],{"class":113},[33,1301,736],{"class":117},[33,1303,1304],{"class":161},"MemoizedChild",[33,1306,527],{"class":139},[33,1308,168],{"class":113},[33,1310,1311],{"class":117},"{handleClick} \u002F>\n",[33,1313,1314],{"class":35,"line":316},[33,1315,308],{"class":117},[33,1317,1318],{"class":35,"line":322},[33,1319,101],{"emptyLinePlaceholder":100},[33,1321,1322],{"class":35,"line":764},[33,1323,1324],{"class":39},"\u002F\u002F ANTI-PATTERN: wrapping trivial computations in useMemo.\n",[33,1326,1327],{"class":35,"line":774},[33,1328,1329],{"class":39},"\u002F\u002F useMemo itself has overhead (storing deps, comparing them every render).\n",[33,1331,1332],{"class":35,"line":783},[33,1333,1334],{"class":39},"\u002F\u002F const sum = useMemo(() => a + b, [a, b]) — slower than const sum = a + b.\n",[33,1336,1337],{"class":35,"line":788},[33,1338,1339],{"class":39},"\u002F\u002F Reserve useMemo for computations CONFIRMED expensive via profiling.\n",[14,1341,1343],{"id":1342},"code-splitting-with-reactlazy-and-suspense","Code Splitting with React.lazy and Suspense",[19,1345,1347],{"filename":1346,"language":22},"code_splitting.js",[24,1348,1350],{"className":26,"code":1349,"language":22,"meta":28,"style":28},"import { lazy, Suspense } from 'react'\n\n\u002F\u002F lazy() defers loading a component's code until it's actually rendered.\n\u002F\u002F The bundler splits it into a separate chunk, fetched on demand.\n\n\u002F\u002F Route-level splitting — highest leverage:\nconst AdminPanel = lazy(() => import('.\u002FAdminPanel'))\nconst SettingsPage = lazy(() => import('.\u002FSettingsPage'))\nconst ReportsPage = lazy(() => import('.\u002FReportsPage'))\n\nfunction App({ isAdmin }) {\n  return (\n    \u003Cdiv>\n      \u003CHeader \u002F>\n      {isAdmin && (\n        \u003CSuspense fallback={\u003CSpinner \u002F>}>\n          \u003CAdminPanel \u002F>\n        \u003C\u002FSuspense>\n      )}\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Non-admin users NEVER download AdminPanel's code — it's a separate chunk\n\u002F\u002F fetched only if an admin user renders that branch.\n\n\u002F\u002F ANTI-PATTERN: lazy-loading a component WITHOUT a \u003CSuspense> ancestor.\n\u002F\u002F This throws at runtime (not a warning) the first time the component\n\u002F\u002F needs to load — every lazy() needs a \u003CSuspense> somewhere above it.\n",[30,1351,1352,1363,1367,1372,1377,1381,1386,1413,1437,1461,1465,1478,1484,1492,1501,1511,1533,1543,1552,1557,1565,1569,1573,1577,1582,1587,1591,1596,1601],{"__ignoreMap":28},[33,1353,1354,1356,1359,1361],{"class":35,"line":36},[33,1355,114],{"class":113},[33,1357,1358],{"class":117}," { lazy, Suspense } ",[33,1360,121],{"class":113},[33,1362,125],{"class":124},[33,1364,1365],{"class":35,"line":43},[33,1366,101],{"emptyLinePlaceholder":100},[33,1368,1369],{"class":35,"line":49},[33,1370,1371],{"class":39},"\u002F\u002F lazy() defers loading a component's code until it's actually rendered.\n",[33,1373,1374],{"class":35,"line":55},[33,1375,1376],{"class":39},"\u002F\u002F The bundler splits it into a separate chunk, fetched on demand.\n",[33,1378,1379],{"class":35,"line":61},[33,1380,101],{"emptyLinePlaceholder":100},[33,1382,1383],{"class":35,"line":67},[33,1384,1385],{"class":39},"\u002F\u002F Route-level splitting — highest leverage:\n",[33,1387,1388,1390,1393,1395,1398,1400,1402,1405,1407,1410],{"class":35,"line":73},[33,1389,463],{"class":113},[33,1391,1392],{"class":161}," AdminPanel",[33,1394,469],{"class":113},[33,1396,1397],{"class":139}," lazy",[33,1399,1061],{"class":117},[33,1401,200],{"class":113},[33,1403,1404],{"class":113}," import",[33,1406,245],{"class":117},[33,1408,1409],{"class":124},"'.\u002FAdminPanel'",[33,1411,1412],{"class":117},"))\n",[33,1414,1415,1417,1420,1422,1424,1426,1428,1430,1432,1435],{"class":35,"line":79},[33,1416,463],{"class":113},[33,1418,1419],{"class":161}," SettingsPage",[33,1421,469],{"class":113},[33,1423,1397],{"class":139},[33,1425,1061],{"class":117},[33,1427,200],{"class":113},[33,1429,1404],{"class":113},[33,1431,245],{"class":117},[33,1433,1434],{"class":124},"'.\u002FSettingsPage'",[33,1436,1412],{"class":117},[33,1438,1439,1441,1444,1446,1448,1450,1452,1454,1456,1459],{"class":35,"line":85},[33,1440,463],{"class":113},[33,1442,1443],{"class":161}," ReportsPage",[33,1445,469],{"class":113},[33,1447,1397],{"class":139},[33,1449,1061],{"class":117},[33,1451,200],{"class":113},[33,1453,1404],{"class":113},[33,1455,245],{"class":117},[33,1457,1458],{"class":124},"'.\u002FReportsPage'",[33,1460,1412],{"class":117},[33,1462,1463],{"class":35,"line":91},[33,1464,101],{"emptyLinePlaceholder":100},[33,1466,1467,1469,1471,1473,1476],{"class":35,"line":97},[33,1468,136],{"class":113},[33,1470,140],{"class":139},[33,1472,481],{"class":117},[33,1474,1475],{"class":182},"isAdmin",[33,1477,492],{"class":117},[33,1479,1480,1482],{"class":35,"line":104},[33,1481,149],{"class":113},[33,1483,152],{"class":117},[33,1485,1486,1488,1490],{"class":35,"line":110},[33,1487,158],{"class":117},[33,1489,863],{"class":523},[33,1491,296],{"class":117},[33,1493,1494,1496,1499],{"class":35,"line":128},[33,1495,279],{"class":117},[33,1497,1498],{"class":161},"Header",[33,1500,285],{"class":117},[33,1502,1503,1506,1509],{"class":35,"line":133},[33,1504,1505],{"class":117},"      {isAdmin ",[33,1507,1508],{"class":113},"&&",[33,1510,152],{"class":117},[33,1512,1513,1516,1519,1522,1524,1527,1530],{"class":35,"line":146},[33,1514,1515],{"class":117},"        \u003C",[33,1517,1518],{"class":161},"Suspense",[33,1520,1521],{"class":139}," fallback",[33,1523,168],{"class":113},[33,1525,1526],{"class":117},"{\u003C",[33,1528,1529],{"class":161},"Spinner",[33,1531,1532],{"class":117}," \u002F>}>\n",[33,1534,1535,1538,1541],{"class":35,"line":155},[33,1536,1537],{"class":117},"          \u003C",[33,1539,1540],{"class":161},"AdminPanel",[33,1542,285],{"class":117},[33,1544,1545,1548,1550],{"class":35,"line":206},[33,1546,1547],{"class":117},"        \u003C\u002F",[33,1549,1518],{"class":161},[33,1551,296],{"class":117},[33,1553,1554],{"class":35,"line":212},[33,1555,1556],{"class":117},"      )}\n",[33,1558,1559,1561,1563],{"class":35,"line":218},[33,1560,291],{"class":117},[33,1562,863],{"class":523},[33,1564,296],{"class":117},[33,1566,1567],{"class":35,"line":236},[33,1568,302],{"class":117},[33,1570,1571],{"class":35,"line":264},[33,1572,308],{"class":117},[33,1574,1575],{"class":35,"line":270},[33,1576,101],{"emptyLinePlaceholder":100},[33,1578,1579],{"class":35,"line":276},[33,1580,1581],{"class":39},"\u002F\u002F Non-admin users NEVER download AdminPanel's code — it's a separate chunk\n",[33,1583,1584],{"class":35,"line":288},[33,1585,1586],{"class":39},"\u002F\u002F fetched only if an admin user renders that branch.\n",[33,1588,1589],{"class":35,"line":299},[33,1590,101],{"emptyLinePlaceholder":100},[33,1592,1593],{"class":35,"line":305},[33,1594,1595],{"class":39},"\u002F\u002F ANTI-PATTERN: lazy-loading a component WITHOUT a \u003CSuspense> ancestor.\n",[33,1597,1598],{"class":35,"line":311},[33,1599,1600],{"class":39},"\u002F\u002F This throws at runtime (not a warning) the first time the component\n",[33,1602,1603],{"class":35,"line":316},[33,1604,1605],{"class":39},"\u002F\u002F needs to load — every lazy() needs a \u003CSuspense> somewhere above it.\n",[327,1607,1609],{"id":1608},"prefetching-lazy-chunks","Prefetching Lazy Chunks",[19,1611,1613],{"filename":1612,"language":22},"prefetch_chunks.js",[24,1614,1616],{"className":26,"code":1615,"language":22,"meta":28,"style":28},"import { lazy, Suspense } from 'react'\n\n\u002F\u002F Prefetch a chunk on hover\u002Ffocus — the import() starts downloading\n\u002F\u002F before the user clicks, so the chunk is ready by the time they do.\nconst SettingsPage = lazy(() => import('.\u002FSettingsPage'))\n\nfunction App() {\n  const [showSettings, setShowSettings] = useState(false)\n\n  function prefetchSettings() {\n    \u002F\u002F The dynamic import returns a promise — calling it starts the download.\n    \u002F\u002F React.lazy's own import() will reuse the same promise (module caching).\n    import('.\u002FSettingsPage')\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton\n        onClick={() => setShowSettings(true)}\n        onMouseEnter={prefetchSettings}    \u002F\u002F prefetch on hover\n        onFocus={prefetchSettings}         \u002F\u002F prefetch on keyboard focus\n      >\n        Open Settings\n      \u003C\u002Fbutton>\n      {showSettings && (\n        \u003CSuspense fallback={\u003CSpinner \u002F>}>\n          \u003CSettingsPage \u002F>\n        \u003C\u002FSuspense>\n      )}\n    \u003C\u002Fdiv>\n  )\n}\n",[30,1617,1618,1628,1632,1637,1642,1664,1668,1676,1703,1707,1717,1722,1727,1738,1743,1747,1753,1761,1768,1790,1803,1816,1821,1826,1835,1844,1860,1869,1877,1881,1889,1893],{"__ignoreMap":28},[33,1619,1620,1622,1624,1626],{"class":35,"line":36},[33,1621,114],{"class":113},[33,1623,1358],{"class":117},[33,1625,121],{"class":113},[33,1627,125],{"class":124},[33,1629,1630],{"class":35,"line":43},[33,1631,101],{"emptyLinePlaceholder":100},[33,1633,1634],{"class":35,"line":49},[33,1635,1636],{"class":39},"\u002F\u002F Prefetch a chunk on hover\u002Ffocus — the import() starts downloading\n",[33,1638,1639],{"class":35,"line":55},[33,1640,1641],{"class":39},"\u002F\u002F before the user clicks, so the chunk is ready by the time they do.\n",[33,1643,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662],{"class":35,"line":61},[33,1645,463],{"class":113},[33,1647,1419],{"class":161},[33,1649,469],{"class":113},[33,1651,1397],{"class":139},[33,1653,1061],{"class":117},[33,1655,200],{"class":113},[33,1657,1404],{"class":113},[33,1659,245],{"class":117},[33,1661,1434],{"class":124},[33,1663,1412],{"class":117},[33,1665,1666],{"class":35,"line":67},[33,1667,101],{"emptyLinePlaceholder":100},[33,1669,1670,1672,1674],{"class":35,"line":73},[33,1671,136],{"class":113},[33,1673,140],{"class":139},[33,1675,143],{"class":117},[33,1677,1678,1680,1682,1685,1687,1690,1692,1694,1696,1698,1701],{"class":35,"line":79},[33,1679,617],{"class":113},[33,1681,620],{"class":117},[33,1683,1684],{"class":161},"showSettings",[33,1686,186],{"class":117},[33,1688,1689],{"class":161},"setShowSettings",[33,1691,631],{"class":117},[33,1693,168],{"class":113},[33,1695,636],{"class":139},[33,1697,245],{"class":117},[33,1699,1700],{"class":161},"false",[33,1702,261],{"class":117},[33,1704,1705],{"class":35,"line":85},[33,1706,101],{"emptyLinePlaceholder":100},[33,1708,1709,1712,1715],{"class":35,"line":91},[33,1710,1711],{"class":113},"  function",[33,1713,1714],{"class":139}," prefetchSettings",[33,1716,143],{"class":117},[33,1718,1719],{"class":35,"line":97},[33,1720,1721],{"class":39},"    \u002F\u002F The dynamic import returns a promise — calling it starts the download.\n",[33,1723,1724],{"class":35,"line":104},[33,1725,1726],{"class":39},"    \u002F\u002F React.lazy's own import() will reuse the same promise (module caching).\n",[33,1728,1729,1732,1734,1736],{"class":35,"line":110},[33,1730,1731],{"class":113},"    import",[33,1733,245],{"class":117},[33,1735,1434],{"class":124},[33,1737,261],{"class":117},[33,1739,1740],{"class":35,"line":128},[33,1741,1742],{"class":117},"  }\n",[33,1744,1745],{"class":35,"line":133},[33,1746,101],{"emptyLinePlaceholder":100},[33,1748,1749,1751],{"class":35,"line":146},[33,1750,149],{"class":113},[33,1752,152],{"class":117},[33,1754,1755,1757,1759],{"class":35,"line":155},[33,1756,158],{"class":117},[33,1758,863],{"class":523},[33,1760,296],{"class":117},[33,1762,1763,1765],{"class":35,"line":206},[33,1764,279],{"class":117},[33,1766,1767],{"class":523},"button\n",[33,1769,1770,1773,1775,1777,1779,1782,1784,1787],{"class":35,"line":212},[33,1771,1772],{"class":139},"        onClick",[33,1774,168],{"class":113},[33,1776,532],{"class":117},[33,1778,200],{"class":113},[33,1780,1781],{"class":139}," setShowSettings",[33,1783,245],{"class":117},[33,1785,1786],{"class":161},"true",[33,1788,1789],{"class":117},")}\n",[33,1791,1792,1795,1797,1800],{"class":35,"line":218},[33,1793,1794],{"class":139},"        onMouseEnter",[33,1796,168],{"class":113},[33,1798,1799],{"class":117},"{prefetchSettings}    ",[33,1801,1802],{"class":39},"\u002F\u002F prefetch on hover\n",[33,1804,1805,1808,1810,1813],{"class":35,"line":236},[33,1806,1807],{"class":139},"        onFocus",[33,1809,168],{"class":113},[33,1811,1812],{"class":117},"{prefetchSettings}         ",[33,1814,1815],{"class":39},"\u002F\u002F prefetch on keyboard focus\n",[33,1817,1818],{"class":35,"line":264},[33,1819,1820],{"class":117},"      >\n",[33,1822,1823],{"class":35,"line":270},[33,1824,1825],{"class":117},"        Open Settings\n",[33,1827,1828,1830,1833],{"class":35,"line":276},[33,1829,767],{"class":117},[33,1831,1832],{"class":523},"button",[33,1834,296],{"class":117},[33,1836,1837,1840,1842],{"class":35,"line":288},[33,1838,1839],{"class":117},"      {showSettings ",[33,1841,1508],{"class":113},[33,1843,152],{"class":117},[33,1845,1846,1848,1850,1852,1854,1856,1858],{"class":35,"line":299},[33,1847,1515],{"class":117},[33,1849,1518],{"class":161},[33,1851,1521],{"class":139},[33,1853,168],{"class":113},[33,1855,1526],{"class":117},[33,1857,1529],{"class":161},[33,1859,1532],{"class":117},[33,1861,1862,1864,1867],{"class":35,"line":305},[33,1863,1537],{"class":117},[33,1865,1866],{"class":161},"SettingsPage",[33,1868,285],{"class":117},[33,1870,1871,1873,1875],{"class":35,"line":311},[33,1872,1547],{"class":117},[33,1874,1518],{"class":161},[33,1876,296],{"class":117},[33,1878,1879],{"class":35,"line":316},[33,1880,1556],{"class":117},[33,1882,1883,1885,1887],{"class":35,"line":322},[33,1884,291],{"class":117},[33,1886,863],{"class":523},[33,1888,296],{"class":117},[33,1890,1891],{"class":35,"line":764},[33,1892,302],{"class":117},[33,1894,1895],{"class":35,"line":774},[33,1896,308],{"class":117},[14,1898,1900],{"id":1899},"virtualization-with-react-window","Virtualization with react-window",[19,1902,1904],{"filename":1903,"language":22},"virtualization.js",[24,1905,1907],{"className":26,"code":1906,"language":22,"meta":28,"style":28},"import { FixedSizeList, VariableSizeList } from 'react-window'\n\n\u002F\u002F Without virtualization: 10,000 rows = 10,000 DOM nodes — browser creates,\n\u002F\u002F styles, and lays out every one even though most are scrolled out of view.\n\u002F\u002F With virtualization: only the visible rows (+ small overscan buffer) exist.\n\nfunction ProductList({ products }) {\n  return (\n    \u003CFixedSizeList\n      height={600}\n      itemCount={products.length}\n      itemSize={50}        \u002F\u002F fixed row height in px\n      width=\"100%\"\n      overscanCount={5}    \u002F\u002F render 5 extra rows above\u002Fbelow for smooth scroll\n    >\n      {({ index, style }) => (\n        \u002F\u002F `style` is REQUIRED — react-window uses absolute positioning to\n        \u002F\u002F place each row at its correct scroll offset. Do NOT override top\u002Fleft.\n        \u003Cdiv style={style} key={products[index].id}>\n          {products[index].name} — ${products[index].price}\n        \u003C\u002Fdiv>\n      )}\n    \u003C\u002FFixedSizeList>\n  )\n}\n\n\u002F\u002F Variable row heights — provide an itemSize function, not a number:\nfunction VariableProductList({ products }) {\n  const getItemSize = (index) => {\n    \u002F\u002F Height depends on content length — measure or compute per item.\n    return products[index].description.length > 100 ? 80 : 50\n  }\n\n  return (\n    \u003CVariableSizeList\n      height={600}\n      itemCount={products.length}\n      itemSize={getItemSize}\n      width=\"100%\"\n    >\n      {({ index, style }) => (\n        \u003Cdiv style={style}>{products[index].name}\u003C\u002Fdiv>\n      )}\n    \u003C\u002FVariableSizeList>\n  )\n}\n",[30,1908,1909,1921,1925,1930,1935,1940,1944,1957,1963,1970,1985,2000,2018,2028,2046,2051,2071,2076,2081,2103,2108,2116,2120,2129,2133,2137,2141,2146,2159,2179,2184,2210,2214,2218,2224,2232,2245,2258,2268,2277,2282,2299,2317,2322,2332,2337],{"__ignoreMap":28},[33,1910,1911,1913,1916,1918],{"class":35,"line":36},[33,1912,114],{"class":113},[33,1914,1915],{"class":117}," { FixedSizeList, VariableSizeList } ",[33,1917,121],{"class":113},[33,1919,1920],{"class":124}," 'react-window'\n",[33,1922,1923],{"class":35,"line":43},[33,1924,101],{"emptyLinePlaceholder":100},[33,1926,1927],{"class":35,"line":49},[33,1928,1929],{"class":39},"\u002F\u002F Without virtualization: 10,000 rows = 10,000 DOM nodes — browser creates,\n",[33,1931,1932],{"class":35,"line":55},[33,1933,1934],{"class":39},"\u002F\u002F styles, and lays out every one even though most are scrolled out of view.\n",[33,1936,1937],{"class":35,"line":61},[33,1938,1939],{"class":39},"\u002F\u002F With virtualization: only the visible rows (+ small overscan buffer) exist.\n",[33,1941,1942],{"class":35,"line":67},[33,1943,101],{"emptyLinePlaceholder":100},[33,1945,1946,1948,1951,1953,1955],{"class":35,"line":73},[33,1947,136],{"class":113},[33,1949,1950],{"class":139}," ProductList",[33,1952,481],{"class":117},[33,1954,610],{"class":182},[33,1956,492],{"class":117},[33,1958,1959,1961],{"class":35,"line":79},[33,1960,149],{"class":113},[33,1962,152],{"class":117},[33,1964,1965,1967],{"class":35,"line":85},[33,1966,158],{"class":117},[33,1968,1969],{"class":161},"FixedSizeList\n",[33,1971,1972,1975,1977,1980,1983],{"class":35,"line":91},[33,1973,1974],{"class":139},"      height",[33,1976,168],{"class":113},[33,1978,1979],{"class":117},"{",[33,1981,1982],{"class":161},"600",[33,1984,308],{"class":117},[33,1986,1987,1990,1992,1995,1998],{"class":35,"line":97},[33,1988,1989],{"class":139},"      itemCount",[33,1991,168],{"class":113},[33,1993,1994],{"class":117},"{products.",[33,1996,1997],{"class":161},"length",[33,1999,308],{"class":117},[33,2001,2002,2005,2007,2009,2012,2015],{"class":35,"line":104},[33,2003,2004],{"class":139},"      itemSize",[33,2006,168],{"class":113},[33,2008,1979],{"class":117},[33,2010,2011],{"class":161},"50",[33,2013,2014],{"class":117},"}        ",[33,2016,2017],{"class":39},"\u002F\u002F fixed row height in px\n",[33,2019,2020,2023,2025],{"class":35,"line":110},[33,2021,2022],{"class":139},"      width",[33,2024,168],{"class":113},[33,2026,2027],{"class":124},"\"100%\"\n",[33,2029,2030,2033,2035,2037,2040,2043],{"class":35,"line":128},[33,2031,2032],{"class":139},"      overscanCount",[33,2034,168],{"class":113},[33,2036,1979],{"class":117},[33,2038,2039],{"class":161},"5",[33,2041,2042],{"class":117},"}    ",[33,2044,2045],{"class":39},"\u002F\u002F render 5 extra rows above\u002Fbelow for smooth scroll\n",[33,2047,2048],{"class":35,"line":133},[33,2049,2050],{"class":117},"    >\n",[33,2052,2053,2056,2059,2061,2064,2067,2069],{"class":35,"line":146},[33,2054,2055],{"class":117},"      {({ ",[33,2057,2058],{"class":182},"index",[33,2060,186],{"class":117},[33,2062,2063],{"class":182},"style",[33,2065,2066],{"class":117}," }) ",[33,2068,200],{"class":113},[33,2070,152],{"class":117},[33,2072,2073],{"class":35,"line":155},[33,2074,2075],{"class":39},"        \u002F\u002F `style` is REQUIRED — react-window uses absolute positioning to\n",[33,2077,2078],{"class":35,"line":206},[33,2079,2080],{"class":39},"        \u002F\u002F place each row at its correct scroll offset. Do NOT override top\u002Fleft.\n",[33,2082,2083,2085,2087,2090,2092,2095,2098,2100],{"class":35,"line":212},[33,2084,1515],{"class":117},[33,2086,863],{"class":523},[33,2088,2089],{"class":139}," style",[33,2091,168],{"class":113},[33,2093,2094],{"class":117},"{style} ",[33,2096,2097],{"class":139},"key",[33,2099,168],{"class":113},[33,2101,2102],{"class":117},"{products[index].id}>\n",[33,2104,2105],{"class":35,"line":218},[33,2106,2107],{"class":117},"          {products[index].name} — ${products[index].price}\n",[33,2109,2110,2112,2114],{"class":35,"line":236},[33,2111,1547],{"class":117},[33,2113,863],{"class":523},[33,2115,296],{"class":117},[33,2117,2118],{"class":35,"line":264},[33,2119,1556],{"class":117},[33,2121,2122,2124,2127],{"class":35,"line":270},[33,2123,291],{"class":117},[33,2125,2126],{"class":161},"FixedSizeList",[33,2128,296],{"class":117},[33,2130,2131],{"class":35,"line":276},[33,2132,302],{"class":117},[33,2134,2135],{"class":35,"line":288},[33,2136,308],{"class":117},[33,2138,2139],{"class":35,"line":299},[33,2140,101],{"emptyLinePlaceholder":100},[33,2142,2143],{"class":35,"line":305},[33,2144,2145],{"class":39},"\u002F\u002F Variable row heights — provide an itemSize function, not a number:\n",[33,2147,2148,2150,2153,2155,2157],{"class":35,"line":311},[33,2149,136],{"class":113},[33,2151,2152],{"class":139}," VariableProductList",[33,2154,481],{"class":117},[33,2156,610],{"class":182},[33,2158,492],{"class":117},[33,2160,2161,2163,2166,2168,2171,2173,2175,2177],{"class":35,"line":316},[33,2162,617],{"class":113},[33,2164,2165],{"class":139}," getItemSize",[33,2167,469],{"class":113},[33,2169,2170],{"class":117}," (",[33,2172,2058],{"class":182},[33,2174,197],{"class":117},[33,2176,200],{"class":113},[33,2178,203],{"class":117},[33,2180,2181],{"class":35,"line":322},[33,2182,2183],{"class":39},"    \u002F\u002F Height depends on content length — measure or compute per item.\n",[33,2185,2186,2188,2191,2193,2196,2199,2202,2205,2207],{"class":35,"line":764},[33,2187,1070],{"class":113},[33,2189,2190],{"class":117}," products[index].description.",[33,2192,1997],{"class":161},[33,2194,2195],{"class":113}," >",[33,2197,2198],{"class":161}," 100",[33,2200,2201],{"class":113}," ?",[33,2203,2204],{"class":161}," 80",[33,2206,880],{"class":113},[33,2208,2209],{"class":161}," 50\n",[33,2211,2212],{"class":35,"line":774},[33,2213,1742],{"class":117},[33,2215,2216],{"class":35,"line":783},[33,2217,101],{"emptyLinePlaceholder":100},[33,2219,2220,2222],{"class":35,"line":788},[33,2221,149],{"class":113},[33,2223,152],{"class":117},[33,2225,2227,2229],{"class":35,"line":2226},35,[33,2228,158],{"class":117},[33,2230,2231],{"class":161},"VariableSizeList\n",[33,2233,2235,2237,2239,2241,2243],{"class":35,"line":2234},36,[33,2236,1974],{"class":139},[33,2238,168],{"class":113},[33,2240,1979],{"class":117},[33,2242,1982],{"class":161},[33,2244,308],{"class":117},[33,2246,2248,2250,2252,2254,2256],{"class":35,"line":2247},37,[33,2249,1989],{"class":139},[33,2251,168],{"class":113},[33,2253,1994],{"class":117},[33,2255,1997],{"class":161},[33,2257,308],{"class":117},[33,2259,2261,2263,2265],{"class":35,"line":2260},38,[33,2262,2004],{"class":139},[33,2264,168],{"class":113},[33,2266,2267],{"class":117},"{getItemSize}\n",[33,2269,2271,2273,2275],{"class":35,"line":2270},39,[33,2272,2022],{"class":139},[33,2274,168],{"class":113},[33,2276,2027],{"class":124},[33,2278,2280],{"class":35,"line":2279},40,[33,2281,2050],{"class":117},[33,2283,2285,2287,2289,2291,2293,2295,2297],{"class":35,"line":2284},41,[33,2286,2055],{"class":117},[33,2288,2058],{"class":182},[33,2290,186],{"class":117},[33,2292,2063],{"class":182},[33,2294,2066],{"class":117},[33,2296,200],{"class":113},[33,2298,152],{"class":117},[33,2300,2302,2304,2306,2308,2310,2313,2315],{"class":35,"line":2301},42,[33,2303,1515],{"class":117},[33,2305,863],{"class":523},[33,2307,2089],{"class":139},[33,2309,168],{"class":113},[33,2311,2312],{"class":117},"{style}>{products[index].name}\u003C\u002F",[33,2314,863],{"class":523},[33,2316,296],{"class":117},[33,2318,2320],{"class":35,"line":2319},43,[33,2321,1556],{"class":117},[33,2323,2325,2327,2330],{"class":35,"line":2324},44,[33,2326,291],{"class":117},[33,2328,2329],{"class":161},"VariableSizeList",[33,2331,296],{"class":117},[33,2333,2335],{"class":35,"line":2334},45,[33,2336,302],{"class":117},[33,2338,2340],{"class":35,"line":2339},46,[33,2341,308],{"class":117},[327,2343,2345],{"id":2344},"virtualized-list-with-memoized-rows","Virtualized List with Memoized Rows",[19,2347,2349],{"filename":2348,"language":22},"virtualized_memoized.js",[24,2350,2352],{"className":26,"code":2351,"language":22,"meta":28,"style":28},"import { FixedSizeList } from 'react-window'\nimport { memo, useCallback } from 'react'\n\n\u002F\u002F Memoize the row renderer so react-window can skip re-rendering\n\u002F\u002F rows whose data hasn't changed (e.g. when scrolling).\nconst Row = memo(function Row({ index, style, data }) {\n  const product = data[index]\n  return (\n    \u003Cdiv style={style}>\n      {product.name} — ${product.price}\n    \u003C\u002Fdiv>\n  )\n})\n\nfunction ProductList({ products, onSelect }) {\n  \u002F\u002F Stable callback so Row's memo isn't defeated.\n  const handleSelect = useCallback((id) => onSelect(id), [onSelect])\n\n  \u002F\u002F Pass products via the `itemData` prop — react-window passes it to\n  \u002F\u002F each row as the `data` prop, and it's part of the memo comparison.\n  return (\n    \u003CFixedSizeList\n      height={600}\n      itemCount={products.length}\n      itemSize={50}\n      width=\"100%\"\n      itemData={products}\n    >\n      {Row}\n    \u003C\u002FFixedSizeList>\n  )\n}\n\n\u002F\u002F ANTI-PATTERN: virtualizing a 20-row list. The complexity (absolute\n\u002F\u002F positioning, lost native browser behaviors like Ctrl+F) isn't worth it\n\u002F\u002F until rows are large enough to cause measured jank — typically hundreds+.\n",[30,2353,2354,2365,2376,2380,2385,2390,2422,2434,2440,2453,2458,2466,2470,2474,2478,2494,2499,2522,2526,2531,2536,2542,2548,2560,2572,2584,2592,2602,2606,2611,2619,2623,2627,2631,2636,2641],{"__ignoreMap":28},[33,2355,2356,2358,2361,2363],{"class":35,"line":36},[33,2357,114],{"class":113},[33,2359,2360],{"class":117}," { FixedSizeList } ",[33,2362,121],{"class":113},[33,2364,1920],{"class":124},[33,2366,2367,2369,2372,2374],{"class":35,"line":43},[33,2368,114],{"class":113},[33,2370,2371],{"class":117}," { memo, useCallback } ",[33,2373,121],{"class":113},[33,2375,125],{"class":124},[33,2377,2378],{"class":35,"line":49},[33,2379,101],{"emptyLinePlaceholder":100},[33,2381,2382],{"class":35,"line":55},[33,2383,2384],{"class":39},"\u002F\u002F Memoize the row renderer so react-window can skip re-rendering\n",[33,2386,2387],{"class":35,"line":61},[33,2388,2389],{"class":39},"\u002F\u002F rows whose data hasn't changed (e.g. when scrolling).\n",[33,2391,2392,2394,2397,2399,2401,2403,2405,2407,2409,2411,2413,2415,2417,2420],{"class":35,"line":67},[33,2393,463],{"class":113},[33,2395,2396],{"class":161}," Row",[33,2398,469],{"class":113},[33,2400,472],{"class":139},[33,2402,245],{"class":117},[33,2404,136],{"class":113},[33,2406,2396],{"class":139},[33,2408,481],{"class":117},[33,2410,2058],{"class":182},[33,2412,186],{"class":117},[33,2414,2063],{"class":182},[33,2416,186],{"class":117},[33,2418,2419],{"class":182},"data",[33,2421,492],{"class":117},[33,2423,2424,2426,2429,2431],{"class":35,"line":73},[33,2425,617],{"class":113},[33,2427,2428],{"class":161}," product",[33,2430,469],{"class":113},[33,2432,2433],{"class":117}," data[index]\n",[33,2435,2436,2438],{"class":35,"line":79},[33,2437,149],{"class":113},[33,2439,152],{"class":117},[33,2441,2442,2444,2446,2448,2450],{"class":35,"line":85},[33,2443,158],{"class":117},[33,2445,863],{"class":523},[33,2447,2089],{"class":139},[33,2449,168],{"class":113},[33,2451,2452],{"class":117},"{style}>\n",[33,2454,2455],{"class":35,"line":91},[33,2456,2457],{"class":117},"      {product.name} — ${product.price}\n",[33,2459,2460,2462,2464],{"class":35,"line":97},[33,2461,291],{"class":117},[33,2463,863],{"class":523},[33,2465,296],{"class":117},[33,2467,2468],{"class":35,"line":104},[33,2469,302],{"class":117},[33,2471,2472],{"class":35,"line":110},[33,2473,584],{"class":117},[33,2475,2476],{"class":35,"line":128},[33,2477,101],{"emptyLinePlaceholder":100},[33,2479,2480,2482,2484,2486,2488,2490,2492],{"class":35,"line":133},[33,2481,136],{"class":113},[33,2483,1950],{"class":139},[33,2485,481],{"class":117},[33,2487,610],{"class":182},[33,2489,186],{"class":117},[33,2491,489],{"class":182},[33,2493,492],{"class":117},[33,2495,2496],{"class":35,"line":146},[33,2497,2498],{"class":39},"  \u002F\u002F Stable callback so Row's memo isn't defeated.\n",[33,2500,2501,2503,2505,2507,2509,2511,2513,2515,2517,2519],{"class":35,"line":155},[33,2502,617],{"class":113},[33,2504,669],{"class":161},[33,2506,469],{"class":113},[33,2508,674],{"class":139},[33,2510,677],{"class":117},[33,2512,183],{"class":182},[33,2514,197],{"class":117},[33,2516,200],{"class":113},[33,2518,537],{"class":139},[33,2520,2521],{"class":117},"(id), [onSelect])\n",[33,2523,2524],{"class":35,"line":206},[33,2525,101],{"emptyLinePlaceholder":100},[33,2527,2528],{"class":35,"line":212},[33,2529,2530],{"class":39},"  \u002F\u002F Pass products via the `itemData` prop — react-window passes it to\n",[33,2532,2533],{"class":35,"line":218},[33,2534,2535],{"class":39},"  \u002F\u002F each row as the `data` prop, and it's part of the memo comparison.\n",[33,2537,2538,2540],{"class":35,"line":236},[33,2539,149],{"class":113},[33,2541,152],{"class":117},[33,2543,2544,2546],{"class":35,"line":264},[33,2545,158],{"class":117},[33,2547,1969],{"class":161},[33,2549,2550,2552,2554,2556,2558],{"class":35,"line":270},[33,2551,1974],{"class":139},[33,2553,168],{"class":113},[33,2555,1979],{"class":117},[33,2557,1982],{"class":161},[33,2559,308],{"class":117},[33,2561,2562,2564,2566,2568,2570],{"class":35,"line":276},[33,2563,1989],{"class":139},[33,2565,168],{"class":113},[33,2567,1994],{"class":117},[33,2569,1997],{"class":161},[33,2571,308],{"class":117},[33,2573,2574,2576,2578,2580,2582],{"class":35,"line":288},[33,2575,2004],{"class":139},[33,2577,168],{"class":113},[33,2579,1979],{"class":117},[33,2581,2011],{"class":161},[33,2583,308],{"class":117},[33,2585,2586,2588,2590],{"class":35,"line":299},[33,2587,2022],{"class":139},[33,2589,168],{"class":113},[33,2591,2027],{"class":124},[33,2593,2594,2597,2599],{"class":35,"line":305},[33,2595,2596],{"class":139},"      itemData",[33,2598,168],{"class":113},[33,2600,2601],{"class":117},"{products}\n",[33,2603,2604],{"class":35,"line":311},[33,2605,2050],{"class":117},[33,2607,2608],{"class":35,"line":316},[33,2609,2610],{"class":117},"      {Row}\n",[33,2612,2613,2615,2617],{"class":35,"line":322},[33,2614,291],{"class":117},[33,2616,2126],{"class":161},[33,2618,296],{"class":117},[33,2620,2621],{"class":35,"line":764},[33,2622,302],{"class":117},[33,2624,2625],{"class":35,"line":774},[33,2626,308],{"class":117},[33,2628,2629],{"class":35,"line":783},[33,2630,101],{"emptyLinePlaceholder":100},[33,2632,2633],{"class":35,"line":788},[33,2634,2635],{"class":39},"\u002F\u002F ANTI-PATTERN: virtualizing a 20-row list. The complexity (absolute\n",[33,2637,2638],{"class":35,"line":2226},[33,2639,2640],{"class":39},"\u002F\u002F positioning, lost native browser behaviors like Ctrl+F) isn't worth it\n",[33,2642,2643],{"class":35,"line":2234},[33,2644,2645],{"class":39},"\u002F\u002F until rows are large enough to cause measured jank — typically hundreds+.\n",[14,2647,2649],{"id":2648},"list-optimization-keys-and-structural-sharing","List Optimization — Keys and Structural Sharing",[19,2651,2653],{"filename":2652,"language":22},"list_optimization.js",[24,2654,2656],{"className":26,"code":2655,"language":22,"meta":28,"style":28},"\u002F\u002F KEY DISCIPLINE: stable, unique keys enable React's reconciliation to\n\u002F\u002F reuse DOM nodes instead of destroying and recreating them.\n\n\u002F\u002F BAD: index as key — if items reorder (sort, filter, insert at top),\n\u002F\u002F React reuses the DOM node but associates it with DIFFERENT data ->\n\u002F\u002F stale state, animation glitches, input misassociations.\n{items.map((item, index) => \u003CRow key={index} item={item} \u002F>)}\n\n\u002F\u002F GOOD: stable unique ID as key\n{items.map(item => \u003CRow key={item.id} item={item} \u002F>)}\n\n\u002F\u002F When items CAN'T have stable IDs (truly ephemeral data), use a library\n\u002F\u002F like nanoid or construct a composite key from content fields:\n{items.map(item => \u003CRow key={`${item.sku}-${item.variant}`} item={item} \u002F>)}\n\n\u002F\u002F STRUCTURAL SHARING: when updating a list, avoid replacing the entire array\n\u002F\u002F if only one item changed. React compares element-by-element with ===,\n\u002F\u002F so if only the changed item has a new reference, only that row re-renders.\n\nfunction updateProduct(products, updatedProduct) {\n  \u002F\u002F BAD: Creates a new array with new references for ALL items:\n  return products.map(p => p.id === updatedProduct.id ? updatedProduct : { ...p })\n\n  \u002F\u002F GOOD: Only the changed item gets a new reference; others keep their identity:\n  return products.map(p => p.id === updatedProduct.id ? updatedProduct : p)\n}\n",[30,2657,2658,2663,2668,2672,2677,2682,2687,2726,2730,2735,2764,2768,2773,2778,2832,2836,2841,2846,2851,2855,2873,2878,2918,2922,2927,2956],{"__ignoreMap":28},[33,2659,2660],{"class":35,"line":36},[33,2661,2662],{"class":39},"\u002F\u002F KEY DISCIPLINE: stable, unique keys enable React's reconciliation to\n",[33,2664,2665],{"class":35,"line":43},[33,2666,2667],{"class":39},"\u002F\u002F reuse DOM nodes instead of destroying and recreating them.\n",[33,2669,2670],{"class":35,"line":49},[33,2671,101],{"emptyLinePlaceholder":100},[33,2673,2674],{"class":35,"line":55},[33,2675,2676],{"class":39},"\u002F\u002F BAD: index as key — if items reorder (sort, filter, insert at top),\n",[33,2678,2679],{"class":35,"line":61},[33,2680,2681],{"class":39},"\u002F\u002F React reuses the DOM node but associates it with DIFFERENT data ->\n",[33,2683,2684],{"class":35,"line":67},[33,2685,2686],{"class":39},"\u002F\u002F stale state, animation glitches, input misassociations.\n",[33,2688,2689,2692,2694,2696,2699,2701,2703,2705,2707,2709,2712,2714,2716,2719,2721,2723],{"class":35,"line":73},[33,2690,2691],{"class":117},"{items.",[33,2693,725],{"class":139},[33,2695,677],{"class":117},[33,2697,2698],{"class":182},"item",[33,2700,186],{"class":117},[33,2702,2058],{"class":182},[33,2704,197],{"class":117},[33,2706,200],{"class":113},[33,2708,736],{"class":117},[33,2710,2711],{"class":161},"Row",[33,2713,742],{"class":139},[33,2715,168],{"class":113},[33,2717,2718],{"class":117},"{index} ",[33,2720,2698],{"class":139},[33,2722,168],{"class":113},[33,2724,2725],{"class":117},"{item} \u002F>)}\n",[33,2727,2728],{"class":35,"line":79},[33,2729,101],{"emptyLinePlaceholder":100},[33,2731,2732],{"class":35,"line":85},[33,2733,2734],{"class":39},"\u002F\u002F GOOD: stable unique ID as key\n",[33,2736,2737,2739,2741,2743,2745,2747,2749,2751,2753,2755,2758,2760,2762],{"class":35,"line":91},[33,2738,2691],{"class":117},[33,2740,725],{"class":139},[33,2742,245],{"class":117},[33,2744,2698],{"class":182},[33,2746,733],{"class":113},[33,2748,736],{"class":117},[33,2750,2711],{"class":161},[33,2752,742],{"class":139},[33,2754,168],{"class":113},[33,2756,2757],{"class":117},"{item.id} ",[33,2759,2698],{"class":139},[33,2761,168],{"class":113},[33,2763,2725],{"class":117},[33,2765,2766],{"class":35,"line":97},[33,2767,101],{"emptyLinePlaceholder":100},[33,2769,2770],{"class":35,"line":104},[33,2771,2772],{"class":39},"\u002F\u002F When items CAN'T have stable IDs (truly ephemeral data), use a library\n",[33,2774,2775],{"class":35,"line":110},[33,2776,2777],{"class":39},"\u002F\u002F like nanoid or construct a composite key from content fields:\n",[33,2779,2780,2782,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2807,2810,2813,2815,2817,2820,2823,2826,2828,2830],{"class":35,"line":128},[33,2781,2691],{"class":117},[33,2783,725],{"class":139},[33,2785,245],{"class":117},[33,2787,2698],{"class":182},[33,2789,733],{"class":113},[33,2791,736],{"class":117},[33,2793,2711],{"class":161},[33,2795,742],{"class":139},[33,2797,168],{"class":113},[33,2799,1979],{"class":117},[33,2801,248],{"class":124},[33,2803,2698],{"class":117},[33,2805,2806],{"class":124},".",[33,2808,2809],{"class":117},"sku",[33,2811,2812],{"class":124},"}-${",[33,2814,2698],{"class":117},[33,2816,2806],{"class":124},[33,2818,2819],{"class":117},"variant",[33,2821,2822],{"class":124},"}`",[33,2824,2825],{"class":117},"} ",[33,2827,2698],{"class":139},[33,2829,168],{"class":113},[33,2831,2725],{"class":117},[33,2833,2834],{"class":35,"line":133},[33,2835,101],{"emptyLinePlaceholder":100},[33,2837,2838],{"class":35,"line":146},[33,2839,2840],{"class":39},"\u002F\u002F STRUCTURAL SHARING: when updating a list, avoid replacing the entire array\n",[33,2842,2843],{"class":35,"line":155},[33,2844,2845],{"class":39},"\u002F\u002F if only one item changed. React compares element-by-element with ===,\n",[33,2847,2848],{"class":35,"line":206},[33,2849,2850],{"class":39},"\u002F\u002F so if only the changed item has a new reference, only that row re-renders.\n",[33,2852,2853],{"class":35,"line":212},[33,2854,101],{"emptyLinePlaceholder":100},[33,2856,2857,2859,2862,2864,2866,2868,2871],{"class":35,"line":218},[33,2858,136],{"class":113},[33,2860,2861],{"class":139}," updateProduct",[33,2863,245],{"class":117},[33,2865,610],{"class":182},[33,2867,186],{"class":117},[33,2869,2870],{"class":182},"updatedProduct",[33,2872,233],{"class":117},[33,2874,2875],{"class":35,"line":236},[33,2876,2877],{"class":39},"  \u002F\u002F BAD: Creates a new array with new references for ALL items:\n",[33,2879,2880,2882,2885,2887,2889,2891,2893,2896,2898,2901,2903,2906,2909,2912,2915],{"class":35,"line":264},[33,2881,149],{"class":113},[33,2883,2884],{"class":117}," products.",[33,2886,725],{"class":139},[33,2888,245],{"class":117},[33,2890,730],{"class":182},[33,2892,733],{"class":113},[33,2894,2895],{"class":117}," p.id ",[33,2897,227],{"class":113},[33,2899,2900],{"class":117}," updatedProduct.id ",[33,2902,874],{"class":113},[33,2904,2905],{"class":117}," updatedProduct ",[33,2907,2908],{"class":113},":",[33,2910,2911],{"class":117}," { ",[33,2913,2914],{"class":113},"...",[33,2916,2917],{"class":117},"p })\n",[33,2919,2920],{"class":35,"line":270},[33,2921,101],{"emptyLinePlaceholder":100},[33,2923,2924],{"class":35,"line":276},[33,2925,2926],{"class":39},"  \u002F\u002F GOOD: Only the changed item gets a new reference; others keep their identity:\n",[33,2928,2929,2931,2933,2935,2937,2939,2941,2943,2945,2947,2949,2951,2953],{"class":35,"line":288},[33,2930,149],{"class":113},[33,2932,2884],{"class":117},[33,2934,725],{"class":139},[33,2936,245],{"class":117},[33,2938,730],{"class":182},[33,2940,733],{"class":113},[33,2942,2895],{"class":117},[33,2944,227],{"class":113},[33,2946,2900],{"class":117},[33,2948,874],{"class":113},[33,2950,2905],{"class":117},[33,2952,2908],{"class":113},[33,2954,2955],{"class":117}," p)\n",[33,2957,2958],{"class":35,"line":299},[33,2959,308],{"class":117},[14,2961,2963],{"id":2962},"bundle-analysis","Bundle Analysis",[19,2965,2968],{"filename":2966,"language":2967},"bundle_analysis.sh","bash",[24,2969,2972],{"className":2970,"code":2971,"language":2967,"meta":28,"style":28},"language-bash shiki shiki-themes github-light github-dark","# Webpack — visualize what's in your bundle:\nnpx webpack-bundle-analyzer stats.json\n\n# Generate stats:\nwebpack --profile --json > stats.json\n\n# Vite — rollup-plugin-visualizer:\nnpm install -D rollup-plugin-visualizer\n# Add to vite.config.ts, then build -> opens an interactive treemap.\n\n# Next.js — built-in analyzer:\nANALYZE=true npm run build\n\n# Source-map-explorer — works with any bundler that produces source maps:\nnpx source-map-explorer dist\u002Fassets\u002F*.js\n\n# What to look for:\n# - Large dependencies that could be lazy-loaded or replaced\n# - Duplicate versions of the same package (check package-lock.json)\n# - Code that should be server-only shipping to the client (Ch 22)\n# - Tree-shaking failures: entire libraries imported when only one function is used\n",[30,2973,2974,2979,2990,2994,2999,3014,3018,3023,3037,3042,3046,3051,3069,3073,3078,3094,3098,3103,3108,3113,3118],{"__ignoreMap":28},[33,2975,2976],{"class":35,"line":36},[33,2977,2978],{"class":39},"# Webpack — visualize what's in your bundle:\n",[33,2980,2981,2984,2987],{"class":35,"line":43},[33,2982,2983],{"class":139},"npx",[33,2985,2986],{"class":124}," webpack-bundle-analyzer",[33,2988,2989],{"class":124}," stats.json\n",[33,2991,2992],{"class":35,"line":49},[33,2993,101],{"emptyLinePlaceholder":100},[33,2995,2996],{"class":35,"line":55},[33,2997,2998],{"class":39},"# Generate stats:\n",[33,3000,3001,3004,3007,3010,3012],{"class":35,"line":61},[33,3002,3003],{"class":139},"webpack",[33,3005,3006],{"class":161}," --profile",[33,3008,3009],{"class":161}," --json",[33,3011,2195],{"class":113},[33,3013,2989],{"class":124},[33,3015,3016],{"class":35,"line":67},[33,3017,101],{"emptyLinePlaceholder":100},[33,3019,3020],{"class":35,"line":73},[33,3021,3022],{"class":39},"# Vite — rollup-plugin-visualizer:\n",[33,3024,3025,3028,3031,3034],{"class":35,"line":79},[33,3026,3027],{"class":139},"npm",[33,3029,3030],{"class":124}," install",[33,3032,3033],{"class":161}," -D",[33,3035,3036],{"class":124}," rollup-plugin-visualizer\n",[33,3038,3039],{"class":35,"line":85},[33,3040,3041],{"class":39},"# Add to vite.config.ts, then build -> opens an interactive treemap.\n",[33,3043,3044],{"class":35,"line":91},[33,3045,101],{"emptyLinePlaceholder":100},[33,3047,3048],{"class":35,"line":97},[33,3049,3050],{"class":39},"# Next.js — built-in analyzer:\n",[33,3052,3053,3056,3058,3060,3063,3066],{"class":35,"line":104},[33,3054,3055],{"class":117},"ANALYZE",[33,3057,168],{"class":113},[33,3059,1786],{"class":124},[33,3061,3062],{"class":139}," npm",[33,3064,3065],{"class":124}," run",[33,3067,3068],{"class":124}," build\n",[33,3070,3071],{"class":35,"line":110},[33,3072,101],{"emptyLinePlaceholder":100},[33,3074,3075],{"class":35,"line":128},[33,3076,3077],{"class":39},"# Source-map-explorer — works with any bundler that produces source maps:\n",[33,3079,3080,3082,3085,3088,3091],{"class":35,"line":133},[33,3081,2983],{"class":139},[33,3083,3084],{"class":124}," source-map-explorer",[33,3086,3087],{"class":124}," dist\u002Fassets\u002F",[33,3089,3090],{"class":161},"*",[33,3092,3093],{"class":124},".js\n",[33,3095,3096],{"class":35,"line":146},[33,3097,101],{"emptyLinePlaceholder":100},[33,3099,3100],{"class":35,"line":155},[33,3101,3102],{"class":39},"# What to look for:\n",[33,3104,3105],{"class":35,"line":206},[33,3106,3107],{"class":39},"# - Large dependencies that could be lazy-loaded or replaced\n",[33,3109,3110],{"class":35,"line":212},[33,3111,3112],{"class":39},"# - Duplicate versions of the same package (check package-lock.json)\n",[33,3114,3115],{"class":35,"line":218},[33,3116,3117],{"class":39},"# - Code that should be server-only shipping to the client (Ch 22)\n",[33,3119,3120],{"class":35,"line":236},[33,3121,3122],{"class":39},"# - Tree-shaking failures: entire libraries imported when only one function is used\n",[327,3124,3126],{"id":3125},"tree-shaking","Tree Shaking",[19,3128,3130],{"filename":3129,"language":22},"tree_shaking.js",[24,3131,3133],{"className":26,"code":3132,"language":22,"meta":28,"style":28},"\u002F\u002F BAD: imports the entire lodash library (even with tree-shaking, many\n\u002F\u002F bundlers can't fully eliminate unused parts of CommonJS modules).\nimport _ from 'lodash'\nconst debouncedFn = _.debounce(fn, 300)\n\n\u002F\u002F GOOD: import only the specific function — guaranteed small footprint.\nimport debounce from 'lodash\u002Fdebounce'  \u002F\u002F or 'lodash-es\u002Fdebounce' for ESM\nconst debouncedFn = debounce(fn, 300)\n\n\u002F\u002F BETTER: use a tree-shakeable alternative.\nimport { debounce } from 'es-toolkit'  \u002F\u002F modern, tree-shakeable, smaller\n",[30,3134,3135,3140,3145,3157,3180,3184,3189,3204,3221,3225,3230],{"__ignoreMap":28},[33,3136,3137],{"class":35,"line":36},[33,3138,3139],{"class":39},"\u002F\u002F BAD: imports the entire lodash library (even with tree-shaking, many\n",[33,3141,3142],{"class":35,"line":43},[33,3143,3144],{"class":39},"\u002F\u002F bundlers can't fully eliminate unused parts of CommonJS modules).\n",[33,3146,3147,3149,3152,3154],{"class":35,"line":49},[33,3148,114],{"class":113},[33,3150,3151],{"class":117}," _ ",[33,3153,121],{"class":113},[33,3155,3156],{"class":124}," 'lodash'\n",[33,3158,3159,3161,3164,3166,3169,3172,3175,3178],{"class":35,"line":55},[33,3160,463],{"class":113},[33,3162,3163],{"class":161}," debouncedFn",[33,3165,469],{"class":113},[33,3167,3168],{"class":117}," _.",[33,3170,3171],{"class":139},"debounce",[33,3173,3174],{"class":117},"(fn, ",[33,3176,3177],{"class":161},"300",[33,3179,261],{"class":117},[33,3181,3182],{"class":35,"line":61},[33,3183,101],{"emptyLinePlaceholder":100},[33,3185,3186],{"class":35,"line":67},[33,3187,3188],{"class":39},"\u002F\u002F GOOD: import only the specific function — guaranteed small footprint.\n",[33,3190,3191,3193,3196,3198,3201],{"class":35,"line":73},[33,3192,114],{"class":113},[33,3194,3195],{"class":117}," debounce ",[33,3197,121],{"class":113},[33,3199,3200],{"class":124}," 'lodash\u002Fdebounce'",[33,3202,3203],{"class":39},"  \u002F\u002F or 'lodash-es\u002Fdebounce' for ESM\n",[33,3205,3206,3208,3210,3212,3215,3217,3219],{"class":35,"line":79},[33,3207,463],{"class":113},[33,3209,3163],{"class":161},[33,3211,469],{"class":113},[33,3213,3214],{"class":139}," debounce",[33,3216,3174],{"class":117},[33,3218,3177],{"class":161},[33,3220,261],{"class":117},[33,3222,3223],{"class":35,"line":85},[33,3224,101],{"emptyLinePlaceholder":100},[33,3226,3227],{"class":35,"line":91},[33,3228,3229],{"class":39},"\u002F\u002F BETTER: use a tree-shakeable alternative.\n",[33,3231,3232,3234,3237,3239,3242],{"class":35,"line":97},[33,3233,114],{"class":113},[33,3235,3236],{"class":117}," { debounce } ",[33,3238,121],{"class":113},[33,3240,3241],{"class":124}," 'es-toolkit'",[33,3243,3244],{"class":39},"  \u002F\u002F modern, tree-shakeable, smaller\n",[14,3246,3248],{"id":3247},"moving-state-down-eliminate-re-renders-at-the-source","Moving State Down — Eliminate Re-renders at the Source",[19,3250,3252],{"filename":3251,"language":22},"state_down.js",[24,3253,3255],{"className":26,"code":3254,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: state lives high, so EVERY keystroke re-renders the\n\u002F\u002F entire page, including expensive unrelated siblings.\nfunction ProductPage({ product }) {\n  const [reviewDraft, setReviewDraft] = useState('')\n  return (\n    \u003Cdiv>\n      \u003CProductChart data={product.priceHistory} \u002F>     {\u002F* expensive *\u002F}\n      \u003CRecommendationsPanel productId={product.id} \u002F>  {\u002F* expensive *\u002F}\n      \u003Ctextarea value={reviewDraft} onChange={e => setReviewDraft(e.target.value)} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F FIX: move state down into the one component that needs it.\n\u002F\u002F ProductChart and RecommendationsPanel never re-render on keystrokes at all.\nfunction ProductPage({ product }) {\n  return (\n    \u003Cdiv>\n      \u003CProductChart data={product.priceHistory} \u002F>\n      \u003CRecommendationsPanel productId={product.id} \u002F>\n      \u003CReviewDraftInput \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\nfunction ReviewDraftInput() {\n  const [reviewDraft, setReviewDraft] = useState('')\n  return \u003Ctextarea value={reviewDraft} onChange={e => setReviewDraft(e.target.value)} \u002F>\n}\n",[30,3256,3257,3262,3267,3280,3307,3313,3321,3340,3359,3392,3400,3404,3408,3412,3417,3422,3434,3440,3448,3461,3474,3483,3491,3495,3499,3503,3512,3536,3564],{"__ignoreMap":28},[33,3258,3259],{"class":35,"line":36},[33,3260,3261],{"class":39},"\u002F\u002F ANTI-PATTERN: state lives high, so EVERY keystroke re-renders the\n",[33,3263,3264],{"class":35,"line":43},[33,3265,3266],{"class":39},"\u002F\u002F entire page, including expensive unrelated siblings.\n",[33,3268,3269,3271,3274,3276,3278],{"class":35,"line":49},[33,3270,136],{"class":113},[33,3272,3273],{"class":139}," ProductPage",[33,3275,481],{"class":117},[33,3277,484],{"class":182},[33,3279,492],{"class":117},[33,3281,3282,3284,3286,3289,3291,3294,3296,3298,3300,3302,3305],{"class":35,"line":55},[33,3283,617],{"class":113},[33,3285,620],{"class":117},[33,3287,3288],{"class":161},"reviewDraft",[33,3290,186],{"class":117},[33,3292,3293],{"class":161},"setReviewDraft",[33,3295,631],{"class":117},[33,3297,168],{"class":113},[33,3299,636],{"class":139},[33,3301,245],{"class":117},[33,3303,3304],{"class":124},"''",[33,3306,261],{"class":117},[33,3308,3309,3311],{"class":35,"line":61},[33,3310,149],{"class":113},[33,3312,152],{"class":117},[33,3314,3315,3317,3319],{"class":35,"line":67},[33,3316,158],{"class":117},[33,3318,863],{"class":523},[33,3320,296],{"class":117},[33,3322,3323,3325,3328,3330,3332,3335,3338],{"class":35,"line":73},[33,3324,279],{"class":117},[33,3326,3327],{"class":161},"ProductChart",[33,3329,1181],{"class":139},[33,3331,168],{"class":113},[33,3333,3334],{"class":117},"{product.priceHistory} \u002F>     {",[33,3336,3337],{"class":39},"\u002F* expensive *\u002F",[33,3339,308],{"class":117},[33,3341,3342,3344,3347,3350,3352,3355,3357],{"class":35,"line":79},[33,3343,279],{"class":117},[33,3345,3346],{"class":161},"RecommendationsPanel",[33,3348,3349],{"class":139}," productId",[33,3351,168],{"class":113},[33,3353,3354],{"class":117},"{product.id} \u002F>  {",[33,3356,3337],{"class":39},[33,3358,308],{"class":117},[33,3360,3361,3363,3366,3369,3371,3374,3377,3379,3381,3384,3386,3389],{"class":35,"line":85},[33,3362,279],{"class":117},[33,3364,3365],{"class":523},"textarea",[33,3367,3368],{"class":139}," value",[33,3370,168],{"class":113},[33,3372,3373],{"class":117},"{reviewDraft} ",[33,3375,3376],{"class":139},"onChange",[33,3378,168],{"class":113},[33,3380,1979],{"class":117},[33,3382,3383],{"class":182},"e",[33,3385,733],{"class":113},[33,3387,3388],{"class":139}," setReviewDraft",[33,3390,3391],{"class":117},"(e.target.value)} \u002F>\n",[33,3393,3394,3396,3398],{"class":35,"line":91},[33,3395,291],{"class":117},[33,3397,863],{"class":523},[33,3399,296],{"class":117},[33,3401,3402],{"class":35,"line":97},[33,3403,302],{"class":117},[33,3405,3406],{"class":35,"line":104},[33,3407,308],{"class":117},[33,3409,3410],{"class":35,"line":110},[33,3411,101],{"emptyLinePlaceholder":100},[33,3413,3414],{"class":35,"line":128},[33,3415,3416],{"class":39},"\u002F\u002F FIX: move state down into the one component that needs it.\n",[33,3418,3419],{"class":35,"line":133},[33,3420,3421],{"class":39},"\u002F\u002F ProductChart and RecommendationsPanel never re-render on keystrokes at all.\n",[33,3423,3424,3426,3428,3430,3432],{"class":35,"line":146},[33,3425,136],{"class":113},[33,3427,3273],{"class":139},[33,3429,481],{"class":117},[33,3431,484],{"class":182},[33,3433,492],{"class":117},[33,3435,3436,3438],{"class":35,"line":155},[33,3437,149],{"class":113},[33,3439,152],{"class":117},[33,3441,3442,3444,3446],{"class":35,"line":206},[33,3443,158],{"class":117},[33,3445,863],{"class":523},[33,3447,296],{"class":117},[33,3449,3450,3452,3454,3456,3458],{"class":35,"line":212},[33,3451,279],{"class":117},[33,3453,3327],{"class":161},[33,3455,1181],{"class":139},[33,3457,168],{"class":113},[33,3459,3460],{"class":117},"{product.priceHistory} \u002F>\n",[33,3462,3463,3465,3467,3469,3471],{"class":35,"line":218},[33,3464,279],{"class":117},[33,3466,3346],{"class":161},[33,3468,3349],{"class":139},[33,3470,168],{"class":113},[33,3472,3473],{"class":117},"{product.id} \u002F>\n",[33,3475,3476,3478,3481],{"class":35,"line":236},[33,3477,279],{"class":117},[33,3479,3480],{"class":161},"ReviewDraftInput",[33,3482,285],{"class":117},[33,3484,3485,3487,3489],{"class":35,"line":264},[33,3486,291],{"class":117},[33,3488,863],{"class":523},[33,3490,296],{"class":117},[33,3492,3493],{"class":35,"line":270},[33,3494,302],{"class":117},[33,3496,3497],{"class":35,"line":276},[33,3498,308],{"class":117},[33,3500,3501],{"class":35,"line":288},[33,3502,101],{"emptyLinePlaceholder":100},[33,3504,3505,3507,3510],{"class":35,"line":299},[33,3506,136],{"class":113},[33,3508,3509],{"class":139}," ReviewDraftInput",[33,3511,143],{"class":117},[33,3513,3514,3516,3518,3520,3522,3524,3526,3528,3530,3532,3534],{"class":35,"line":305},[33,3515,617],{"class":113},[33,3517,620],{"class":117},[33,3519,3288],{"class":161},[33,3521,186],{"class":117},[33,3523,3293],{"class":161},[33,3525,631],{"class":117},[33,3527,168],{"class":113},[33,3529,636],{"class":139},[33,3531,245],{"class":117},[33,3533,3304],{"class":124},[33,3535,261],{"class":117},[33,3537,3538,3540,3542,3544,3546,3548,3550,3552,3554,3556,3558,3560,3562],{"class":35,"line":311},[33,3539,149],{"class":113},[33,3541,736],{"class":117},[33,3543,3365],{"class":523},[33,3545,3368],{"class":139},[33,3547,168],{"class":113},[33,3549,3373],{"class":117},[33,3551,3376],{"class":139},[33,3553,168],{"class":113},[33,3555,1979],{"class":117},[33,3557,3383],{"class":182},[33,3559,733],{"class":113},[33,3561,3388],{"class":139},[33,3563,3391],{"class":117},[33,3565,3566],{"class":35,"line":316},[33,3567,308],{"class":117},[327,3569,3571],{"id":3570},"children-as-props-isolate-re-renders","Children as Props — Isolate Re-renders",[19,3573,3575],{"filename":3574,"language":22},"children_props.js",[24,3576,3578],{"className":26,"code":3577,"language":22,"meta":28,"style":28},"\u002F\u002F A parent re-rendering doesn't necessarily re-render JSX that was\n\u002F\u002F constructed and passed in from OUTSIDE that parent's own render.\n\nfunction ScrollableList({ children }) {\n  const [scrollTop, setScrollTop] = useState(0)\n  return (\n    \u003Cdiv onScroll={e => setScrollTop(e.target.scrollTop)}>\n      {children}  {\u002F* this JSX was created by the PARENT of ScrollableList,\n                       not by ScrollableList itself — it doesn't re-render\n                       when ScrollableList's scrollTop state changes *\u002F}\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Usage: \u003CProductGrid \u002F> is created in App's render and passed as children.\n\u002F\u002F ScrollableList's scroll state changes -> ScrollableList re-renders ->\n\u002F\u002F but {children} (ProductGrid) is the SAME element reference -> React skips it.\nfunction App({ products }) {\n  return (\n    \u003CScrollableList>\n      \u003CProductGrid products={products} \u002F>\n    \u003C\u002FScrollableList>\n  )\n}\n\n\u002F\u002F ANTI-PATTERN: rendering children INSIDE the component that owns the\n\u002F\u002F frequently-changing state:\nfunction ScrollableList({ renderItem }) {\n  const [scrollTop, setScrollTop] = useState(0)\n  return \u003Cdiv onScroll={e => setScrollTop(e.target.scrollTop)}>{renderItem()}\u003C\u002Fdiv>\n  \u002F\u002F renderItem() is called during ScrollableList's render -> re-runs on\n  \u002F\u002F every scroll state change -> children re-render. Use children prop instead.\n}\n",[30,3579,3580,3585,3590,3594,3608,3634,3640,3663,3671,3676,3683,3691,3695,3699,3703,3708,3713,3718,3730,3736,3745,3760,3768,3772,3776,3780,3785,3790,3803,3827,3859,3864,3869],{"__ignoreMap":28},[33,3581,3582],{"class":35,"line":36},[33,3583,3584],{"class":39},"\u002F\u002F A parent re-rendering doesn't necessarily re-render JSX that was\n",[33,3586,3587],{"class":35,"line":43},[33,3588,3589],{"class":39},"\u002F\u002F constructed and passed in from OUTSIDE that parent's own render.\n",[33,3591,3592],{"class":35,"line":49},[33,3593,101],{"emptyLinePlaceholder":100},[33,3595,3596,3598,3601,3603,3606],{"class":35,"line":55},[33,3597,136],{"class":113},[33,3599,3600],{"class":139}," ScrollableList",[33,3602,481],{"class":117},[33,3604,3605],{"class":182},"children",[33,3607,492],{"class":117},[33,3609,3610,3612,3614,3617,3619,3622,3624,3626,3628,3630,3632],{"class":35,"line":61},[33,3611,617],{"class":113},[33,3613,620],{"class":117},[33,3615,3616],{"class":161},"scrollTop",[33,3618,186],{"class":117},[33,3620,3621],{"class":161},"setScrollTop",[33,3623,631],{"class":117},[33,3625,168],{"class":113},[33,3627,636],{"class":139},[33,3629,245],{"class":117},[33,3631,1113],{"class":161},[33,3633,261],{"class":117},[33,3635,3636,3638],{"class":35,"line":67},[33,3637,149],{"class":113},[33,3639,152],{"class":117},[33,3641,3642,3644,3646,3649,3651,3653,3655,3657,3660],{"class":35,"line":73},[33,3643,158],{"class":117},[33,3645,863],{"class":523},[33,3647,3648],{"class":139}," onScroll",[33,3650,168],{"class":113},[33,3652,1979],{"class":117},[33,3654,3383],{"class":182},[33,3656,733],{"class":113},[33,3658,3659],{"class":139}," setScrollTop",[33,3661,3662],{"class":117},"(e.target.scrollTop)}>\n",[33,3664,3665,3668],{"class":35,"line":79},[33,3666,3667],{"class":117},"      {children}  {",[33,3669,3670],{"class":39},"\u002F* this JSX was created by the PARENT of ScrollableList,\n",[33,3672,3673],{"class":35,"line":85},[33,3674,3675],{"class":39},"                       not by ScrollableList itself — it doesn't re-render\n",[33,3677,3678,3681],{"class":35,"line":91},[33,3679,3680],{"class":39},"                       when ScrollableList's scrollTop state changes *\u002F",[33,3682,308],{"class":117},[33,3684,3685,3687,3689],{"class":35,"line":97},[33,3686,291],{"class":117},[33,3688,863],{"class":523},[33,3690,296],{"class":117},[33,3692,3693],{"class":35,"line":104},[33,3694,302],{"class":117},[33,3696,3697],{"class":35,"line":110},[33,3698,308],{"class":117},[33,3700,3701],{"class":35,"line":128},[33,3702,101],{"emptyLinePlaceholder":100},[33,3704,3705],{"class":35,"line":133},[33,3706,3707],{"class":39},"\u002F\u002F Usage: \u003CProductGrid \u002F> is created in App's render and passed as children.\n",[33,3709,3710],{"class":35,"line":146},[33,3711,3712],{"class":39},"\u002F\u002F ScrollableList's scroll state changes -> ScrollableList re-renders ->\n",[33,3714,3715],{"class":35,"line":155},[33,3716,3717],{"class":39},"\u002F\u002F but {children} (ProductGrid) is the SAME element reference -> React skips it.\n",[33,3719,3720,3722,3724,3726,3728],{"class":35,"line":206},[33,3721,136],{"class":113},[33,3723,140],{"class":139},[33,3725,481],{"class":117},[33,3727,610],{"class":182},[33,3729,492],{"class":117},[33,3731,3732,3734],{"class":35,"line":212},[33,3733,149],{"class":113},[33,3735,152],{"class":117},[33,3737,3738,3740,3743],{"class":35,"line":218},[33,3739,158],{"class":117},[33,3741,3742],{"class":161},"ScrollableList",[33,3744,296],{"class":117},[33,3746,3747,3749,3752,3755,3757],{"class":35,"line":236},[33,3748,279],{"class":117},[33,3750,3751],{"class":161},"ProductGrid",[33,3753,3754],{"class":139}," products",[33,3756,168],{"class":113},[33,3758,3759],{"class":117},"{products} \u002F>\n",[33,3761,3762,3764,3766],{"class":35,"line":264},[33,3763,291],{"class":117},[33,3765,3742],{"class":161},[33,3767,296],{"class":117},[33,3769,3770],{"class":35,"line":270},[33,3771,302],{"class":117},[33,3773,3774],{"class":35,"line":276},[33,3775,308],{"class":117},[33,3777,3778],{"class":35,"line":288},[33,3779,101],{"emptyLinePlaceholder":100},[33,3781,3782],{"class":35,"line":299},[33,3783,3784],{"class":39},"\u002F\u002F ANTI-PATTERN: rendering children INSIDE the component that owns the\n",[33,3786,3787],{"class":35,"line":305},[33,3788,3789],{"class":39},"\u002F\u002F frequently-changing state:\n",[33,3791,3792,3794,3796,3798,3801],{"class":35,"line":311},[33,3793,136],{"class":113},[33,3795,3600],{"class":139},[33,3797,481],{"class":117},[33,3799,3800],{"class":182},"renderItem",[33,3802,492],{"class":117},[33,3804,3805,3807,3809,3811,3813,3815,3817,3819,3821,3823,3825],{"class":35,"line":316},[33,3806,617],{"class":113},[33,3808,620],{"class":117},[33,3810,3616],{"class":161},[33,3812,186],{"class":117},[33,3814,3621],{"class":161},[33,3816,631],{"class":117},[33,3818,168],{"class":113},[33,3820,636],{"class":139},[33,3822,245],{"class":117},[33,3824,1113],{"class":161},[33,3826,261],{"class":117},[33,3828,3829,3831,3833,3835,3837,3839,3841,3843,3845,3847,3850,3852,3855,3857],{"class":35,"line":322},[33,3830,149],{"class":113},[33,3832,736],{"class":117},[33,3834,863],{"class":523},[33,3836,3648],{"class":139},[33,3838,168],{"class":113},[33,3840,1979],{"class":117},[33,3842,3383],{"class":182},[33,3844,733],{"class":113},[33,3846,3659],{"class":139},[33,3848,3849],{"class":117},"(e.target.scrollTop)}>{",[33,3851,3800],{"class":139},[33,3853,3854],{"class":117},"()}\u003C\u002F",[33,3856,863],{"class":523},[33,3858,296],{"class":117},[33,3860,3861],{"class":35,"line":764},[33,3862,3863],{"class":39},"  \u002F\u002F renderItem() is called during ScrollableList's render -> re-runs on\n",[33,3865,3866],{"class":35,"line":774},[33,3867,3868],{"class":39},"  \u002F\u002F every scroll state change -> children re-render. Use children prop instead.\n",[33,3870,3871],{"class":35,"line":783},[33,3872,308],{"class":117},[14,3874,3876],{"id":3875},"complex-implementation-optimized-data-heavy-dashboard","Complex Implementation — Optimized Data-Heavy Dashboard",[19,3878,3880],{"filename":3879,"language":22},"optimized_dashboard.js",[24,3881,3883],{"className":26,"code":3882,"language":22,"meta":28,"style":28},"import { memo, useMemo, useCallback, useState, useEffect, lazy, Suspense } from 'react'\nimport { FixedSizeList } from 'react-window'\n\n\u002F\u002F --- Lazy-loaded heavy chart library (separate chunk) ---\nconst RevenueChart = lazy(() => import('.\u002FRevenueChart'))\n\n\u002F\u002F --- Memoized row for virtualized list ---\nconst OrderRow = memo(function OrderRow({ order, onSelect, isSelected }) {\n  return (\n    \u003Cdiv style={{ background: isSelected ? '#e0f0ff' : 'transparent' }}>\n      \u003Cspan>{order.id}\u003C\u002Fspan>\n      \u003Cspan>{order.customer}\u003C\u002Fspan>\n      \u003Cspan>${order.total.toFixed(2)}\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n  )\n})\n\n\u002F\u002F --- Dashboard component ---\nfunction OrderDashboard({ orders, currentUser }) {\n  const [selectedId, setSelectedId] = useState(null)\n  const [filterText, setFilterText] = useState('')\n\n  \u002F\u002F Stable callback — OrderRow memo works because this never changes reference.\n  const handleSelect = useCallback((id) => setSelectedId(id), [])\n\n  \u002F\u002F Expensive filter + sort — memoized, only recomputes when orders or filter change.\n  const filteredOrders = useMemo(() => {\n    const lower = filterText.toLowerCase()\n    return orders\n      .filter(o =>\n        o.customer.toLowerCase().includes(lower) ||\n        o.id.toString().includes(lower)\n      )\n      .sort((a, b) => b.date.localeCompare(a.date))\n  }, [orders, filterText])\n\n  \u002F\u002F Expensive aggregate — memoized on filteredOrders.\n  const totalRevenue = useMemo(() =>\n    filteredOrders.reduce((sum, o) => sum + o.total, 0),\n  [filteredOrders])\n\n  \u002F\u002F Filter input state is LOCAL — doesn't trigger RevenueChart re-render.\n  return (\n    \u003Cdiv>\n      \u003Ch1>Orders — ${totalRevenue.toFixed(2)} total\u003C\u002Fh1>\n\n      {\u002F* Heavy chart in its own lazy chunk + Suspense boundary *\u002F}\n      \u003CSuspense fallback={\u003CChartSkeleton \u002F>}>\n        \u003CRevenueChart data={filteredOrders} \u002F>\n      \u003C\u002FSuspense>\n\n      \u003CFilterInput value={filterText} onChange={setFilterText} \u002F>\n\n      {\u002F* Virtualized list — handles 10k+ orders without DOM bloat *\u002F}\n      \u003CFixedSizeList\n        height={500}\n        itemCount={filteredOrders.length}\n        itemSize={40}\n        width=\"100%\"\n        itemData={filteredOrders}\n      >\n        {({ index, style, data }) => (\n          \u003Cdiv style={style}>\n            \u003COrderRow\n              order={data[index]}\n              onSelect={handleSelect}\n              isSelected={data[index].id === selectedId}\n            \u002F>\n          \u003C\u002Fdiv>\n        )}\n      \u003C\u002FFixedSizeList>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Filter input isolated — its state changes don't bubble up to Dashboard.\nconst FilterInput = memo(function FilterInput({ value, onChange }) {\n  return \u003Cinput value={value} onChange={e => onChange(e.target.value)} placeholder=\"Filter…\" \u002F>\n})\n",[30,3884,3885,3896,3906,3910,3915,3939,3943,3948,3979,3985,4011,4024,4037,4061,4069,4073,4077,4081,4086,4104,4128,4154,4158,4163,4185,4189,4194,4211,4230,4237,4253,4272,4287,4292,4322,4327,4331,4336,4352,4385,4390,4394,4399,4405,4413,4435,4439,4450,4468,4482,4491,4496,4518,4523,4533,4540,4555,4570,4585,4595,4606,4611,4633,4646,4655,4666,4677,4693,4699,4709,4715,4724,4733,4738,4743,4748,4754,4783,4826],{"__ignoreMap":28},[33,3886,3887,3889,3892,3894],{"class":35,"line":36},[33,3888,114],{"class":113},[33,3890,3891],{"class":117}," { memo, useMemo, useCallback, useState, useEffect, lazy, Suspense } ",[33,3893,121],{"class":113},[33,3895,125],{"class":124},[33,3897,3898,3900,3902,3904],{"class":35,"line":43},[33,3899,114],{"class":113},[33,3901,2360],{"class":117},[33,3903,121],{"class":113},[33,3905,1920],{"class":124},[33,3907,3908],{"class":35,"line":49},[33,3909,101],{"emptyLinePlaceholder":100},[33,3911,3912],{"class":35,"line":55},[33,3913,3914],{"class":39},"\u002F\u002F --- Lazy-loaded heavy chart library (separate chunk) ---\n",[33,3916,3917,3919,3922,3924,3926,3928,3930,3932,3934,3937],{"class":35,"line":61},[33,3918,463],{"class":113},[33,3920,3921],{"class":161}," RevenueChart",[33,3923,469],{"class":113},[33,3925,1397],{"class":139},[33,3927,1061],{"class":117},[33,3929,200],{"class":113},[33,3931,1404],{"class":113},[33,3933,245],{"class":117},[33,3935,3936],{"class":124},"'.\u002FRevenueChart'",[33,3938,1412],{"class":117},[33,3940,3941],{"class":35,"line":67},[33,3942,101],{"emptyLinePlaceholder":100},[33,3944,3945],{"class":35,"line":73},[33,3946,3947],{"class":39},"\u002F\u002F --- Memoized row for virtualized list ---\n",[33,3949,3950,3952,3955,3957,3959,3961,3963,3965,3967,3969,3971,3973,3975,3977],{"class":35,"line":79},[33,3951,463],{"class":113},[33,3953,3954],{"class":161}," OrderRow",[33,3956,469],{"class":113},[33,3958,472],{"class":139},[33,3960,245],{"class":117},[33,3962,136],{"class":113},[33,3964,3954],{"class":139},[33,3966,481],{"class":117},[33,3968,1086],{"class":182},[33,3970,186],{"class":117},[33,3972,489],{"class":182},[33,3974,186],{"class":117},[33,3976,852],{"class":182},[33,3978,492],{"class":117},[33,3980,3981,3983],{"class":35,"line":85},[33,3982,149],{"class":113},[33,3984,152],{"class":117},[33,3986,3987,3989,3991,3993,3995,3998,4000,4003,4005,4008],{"class":35,"line":91},[33,3988,158],{"class":117},[33,3990,863],{"class":523},[33,3992,2089],{"class":139},[33,3994,168],{"class":113},[33,3996,3997],{"class":117},"{{ background: isSelected ",[33,3999,874],{"class":113},[33,4001,4002],{"class":124}," '#e0f0ff'",[33,4004,880],{"class":113},[33,4006,4007],{"class":124}," 'transparent'",[33,4009,4010],{"class":117}," }}>\n",[33,4012,4013,4015,4017,4020,4022],{"class":35,"line":97},[33,4014,279],{"class":117},[33,4016,33],{"class":523},[33,4018,4019],{"class":117},">{order.id}\u003C\u002F",[33,4021,33],{"class":523},[33,4023,296],{"class":117},[33,4025,4026,4028,4030,4033,4035],{"class":35,"line":104},[33,4027,279],{"class":117},[33,4029,33],{"class":523},[33,4031,4032],{"class":117},">{order.customer}\u003C\u002F",[33,4034,33],{"class":523},[33,4036,296],{"class":117},[33,4038,4039,4041,4043,4046,4049,4051,4054,4057,4059],{"class":35,"line":110},[33,4040,279],{"class":117},[33,4042,33],{"class":523},[33,4044,4045],{"class":117},">${order.total.",[33,4047,4048],{"class":139},"toFixed",[33,4050,245],{"class":117},[33,4052,4053],{"class":161},"2",[33,4055,4056],{"class":117},")}\u003C\u002F",[33,4058,33],{"class":523},[33,4060,296],{"class":117},[33,4062,4063,4065,4067],{"class":35,"line":128},[33,4064,291],{"class":117},[33,4066,863],{"class":523},[33,4068,296],{"class":117},[33,4070,4071],{"class":35,"line":133},[33,4072,302],{"class":117},[33,4074,4075],{"class":35,"line":146},[33,4076,584],{"class":117},[33,4078,4079],{"class":35,"line":155},[33,4080,101],{"emptyLinePlaceholder":100},[33,4082,4083],{"class":35,"line":206},[33,4084,4085],{"class":39},"\u002F\u002F --- Dashboard component ---\n",[33,4087,4088,4090,4093,4095,4097,4099,4102],{"class":35,"line":212},[33,4089,136],{"class":113},[33,4091,4092],{"class":139}," OrderDashboard",[33,4094,481],{"class":117},[33,4096,1034],{"class":182},[33,4098,186],{"class":117},[33,4100,4101],{"class":182},"currentUser",[33,4103,492],{"class":117},[33,4105,4106,4108,4110,4112,4114,4116,4118,4120,4122,4124,4126],{"class":35,"line":218},[33,4107,617],{"class":113},[33,4109,620],{"class":117},[33,4111,623],{"class":161},[33,4113,186],{"class":117},[33,4115,628],{"class":161},[33,4117,631],{"class":117},[33,4119,168],{"class":113},[33,4121,636],{"class":139},[33,4123,245],{"class":117},[33,4125,641],{"class":161},[33,4127,261],{"class":117},[33,4129,4130,4132,4134,4137,4139,4142,4144,4146,4148,4150,4152],{"class":35,"line":236},[33,4131,617],{"class":113},[33,4133,620],{"class":117},[33,4135,4136],{"class":161},"filterText",[33,4138,186],{"class":117},[33,4140,4141],{"class":161},"setFilterText",[33,4143,631],{"class":117},[33,4145,168],{"class":113},[33,4147,636],{"class":139},[33,4149,245],{"class":117},[33,4151,3304],{"class":124},[33,4153,261],{"class":117},[33,4155,4156],{"class":35,"line":264},[33,4157,101],{"emptyLinePlaceholder":100},[33,4159,4160],{"class":35,"line":270},[33,4161,4162],{"class":39},"  \u002F\u002F Stable callback — OrderRow memo works because this never changes reference.\n",[33,4164,4165,4167,4169,4171,4173,4175,4177,4179,4181,4183],{"class":35,"line":276},[33,4166,617],{"class":113},[33,4168,669],{"class":161},[33,4170,469],{"class":113},[33,4172,674],{"class":139},[33,4174,677],{"class":117},[33,4176,183],{"class":182},[33,4178,197],{"class":117},[33,4180,200],{"class":113},[33,4182,686],{"class":139},[33,4184,689],{"class":117},[33,4186,4187],{"class":35,"line":288},[33,4188,101],{"emptyLinePlaceholder":100},[33,4190,4191],{"class":35,"line":299},[33,4192,4193],{"class":39},"  \u002F\u002F Expensive filter + sort — memoized, only recomputes when orders or filter change.\n",[33,4195,4196,4198,4201,4203,4205,4207,4209],{"class":35,"line":305},[33,4197,617],{"class":113},[33,4199,4200],{"class":161}," filteredOrders",[33,4202,469],{"class":113},[33,4204,1058],{"class":139},[33,4206,1061],{"class":117},[33,4208,200],{"class":113},[33,4210,203],{"class":117},[33,4212,4213,4216,4219,4221,4224,4227],{"class":35,"line":311},[33,4214,4215],{"class":113},"    const",[33,4217,4218],{"class":161}," lower",[33,4220,469],{"class":113},[33,4222,4223],{"class":117}," filterText.",[33,4225,4226],{"class":139},"toLowerCase",[33,4228,4229],{"class":117},"()\n",[33,4231,4232,4234],{"class":35,"line":316},[33,4233,1070],{"class":113},[33,4235,4236],{"class":117}," orders\n",[33,4238,4239,4242,4245,4247,4250],{"class":35,"line":322},[33,4240,4241],{"class":117},"      .",[33,4243,4244],{"class":139},"filter",[33,4246,245],{"class":117},[33,4248,4249],{"class":182},"o",[33,4251,4252],{"class":113}," =>\n",[33,4254,4255,4258,4260,4263,4266,4269],{"class":35,"line":764},[33,4256,4257],{"class":117},"        o.customer.",[33,4259,4226],{"class":139},[33,4261,4262],{"class":117},"().",[33,4264,4265],{"class":139},"includes",[33,4267,4268],{"class":117},"(lower) ",[33,4270,4271],{"class":113},"||\n",[33,4273,4274,4277,4280,4282,4284],{"class":35,"line":774},[33,4275,4276],{"class":117},"        o.id.",[33,4278,4279],{"class":139},"toString",[33,4281,4262],{"class":117},[33,4283,4265],{"class":139},[33,4285,4286],{"class":117},"(lower)\n",[33,4288,4289],{"class":35,"line":783},[33,4290,4291],{"class":117},"      )\n",[33,4293,4294,4296,4299,4301,4304,4306,4309,4311,4313,4316,4319],{"class":35,"line":788},[33,4295,4241],{"class":117},[33,4297,4298],{"class":139},"sort",[33,4300,677],{"class":117},[33,4302,4303],{"class":182},"a",[33,4305,186],{"class":117},[33,4307,4308],{"class":182},"b",[33,4310,197],{"class":117},[33,4312,200],{"class":113},[33,4314,4315],{"class":117}," b.date.",[33,4317,4318],{"class":139},"localeCompare",[33,4320,4321],{"class":117},"(a.date))\n",[33,4323,4324],{"class":35,"line":2226},[33,4325,4326],{"class":117},"  }, [orders, filterText])\n",[33,4328,4329],{"class":35,"line":2234},[33,4330,101],{"emptyLinePlaceholder":100},[33,4332,4333],{"class":35,"line":2247},[33,4334,4335],{"class":39},"  \u002F\u002F Expensive aggregate — memoized on filteredOrders.\n",[33,4337,4338,4340,4343,4345,4347,4349],{"class":35,"line":2260},[33,4339,617],{"class":113},[33,4341,4342],{"class":161}," totalRevenue",[33,4344,469],{"class":113},[33,4346,1058],{"class":139},[33,4348,1061],{"class":117},[33,4350,4351],{"class":113},"=>\n",[33,4353,4354,4357,4359,4361,4364,4366,4368,4370,4372,4375,4377,4380,4382],{"class":35,"line":2270},[33,4355,4356],{"class":117},"    filteredOrders.",[33,4358,1076],{"class":139},[33,4360,677],{"class":117},[33,4362,4363],{"class":182},"sum",[33,4365,186],{"class":117},[33,4367,4249],{"class":182},[33,4369,197],{"class":117},[33,4371,200],{"class":113},[33,4373,4374],{"class":117}," sum ",[33,4376,1141],{"class":113},[33,4378,4379],{"class":117}," o.total, ",[33,4381,1113],{"class":161},[33,4383,4384],{"class":117},"),\n",[33,4386,4387],{"class":35,"line":2279},[33,4388,4389],{"class":117},"  [filteredOrders])\n",[33,4391,4392],{"class":35,"line":2284},[33,4393,101],{"emptyLinePlaceholder":100},[33,4395,4396],{"class":35,"line":2301},[33,4397,4398],{"class":39},"  \u002F\u002F Filter input state is LOCAL — doesn't trigger RevenueChart re-render.\n",[33,4400,4401,4403],{"class":35,"line":2319},[33,4402,149],{"class":113},[33,4404,152],{"class":117},[33,4406,4407,4409,4411],{"class":35,"line":2324},[33,4408,158],{"class":117},[33,4410,863],{"class":523},[33,4412,296],{"class":117},[33,4414,4415,4417,4419,4422,4424,4426,4428,4431,4433],{"class":35,"line":2334},[33,4416,279],{"class":117},[33,4418,10],{"class":523},[33,4420,4421],{"class":117},">Orders — ${totalRevenue.",[33,4423,4048],{"class":139},[33,4425,245],{"class":117},[33,4427,4053],{"class":161},[33,4429,4430],{"class":117},")} total\u003C\u002F",[33,4432,10],{"class":523},[33,4434,296],{"class":117},[33,4436,4437],{"class":35,"line":2339},[33,4438,101],{"emptyLinePlaceholder":100},[33,4440,4442,4445,4448],{"class":35,"line":4441},47,[33,4443,4444],{"class":117},"      {",[33,4446,4447],{"class":39},"\u002F* Heavy chart in its own lazy chunk + Suspense boundary *\u002F",[33,4449,308],{"class":117},[33,4451,4453,4455,4457,4459,4461,4463,4466],{"class":35,"line":4452},48,[33,4454,279],{"class":117},[33,4456,1518],{"class":161},[33,4458,1521],{"class":139},[33,4460,168],{"class":113},[33,4462,1526],{"class":117},[33,4464,4465],{"class":161},"ChartSkeleton",[33,4467,1532],{"class":117},[33,4469,4471,4473,4475,4477,4479],{"class":35,"line":4470},49,[33,4472,1515],{"class":117},[33,4474,1178],{"class":161},[33,4476,1181],{"class":139},[33,4478,168],{"class":113},[33,4480,4481],{"class":117},"{filteredOrders} \u002F>\n",[33,4483,4485,4487,4489],{"class":35,"line":4484},50,[33,4486,767],{"class":117},[33,4488,1518],{"class":161},[33,4490,296],{"class":117},[33,4492,4494],{"class":35,"line":4493},51,[33,4495,101],{"emptyLinePlaceholder":100},[33,4497,4499,4501,4504,4506,4508,4511,4513,4515],{"class":35,"line":4498},52,[33,4500,279],{"class":117},[33,4502,4503],{"class":161},"FilterInput",[33,4505,3368],{"class":139},[33,4507,168],{"class":113},[33,4509,4510],{"class":117},"{filterText} ",[33,4512,3376],{"class":139},[33,4514,168],{"class":113},[33,4516,4517],{"class":117},"{setFilterText} \u002F>\n",[33,4519,4521],{"class":35,"line":4520},53,[33,4522,101],{"emptyLinePlaceholder":100},[33,4524,4526,4528,4531],{"class":35,"line":4525},54,[33,4527,4444],{"class":117},[33,4529,4530],{"class":39},"\u002F* Virtualized list — handles 10k+ orders without DOM bloat *\u002F",[33,4532,308],{"class":117},[33,4534,4536,4538],{"class":35,"line":4535},55,[33,4537,279],{"class":117},[33,4539,1969],{"class":161},[33,4541,4543,4546,4548,4550,4553],{"class":35,"line":4542},56,[33,4544,4545],{"class":139},"        height",[33,4547,168],{"class":113},[33,4549,1979],{"class":117},[33,4551,4552],{"class":161},"500",[33,4554,308],{"class":117},[33,4556,4558,4561,4563,4566,4568],{"class":35,"line":4557},57,[33,4559,4560],{"class":139},"        itemCount",[33,4562,168],{"class":113},[33,4564,4565],{"class":117},"{filteredOrders.",[33,4567,1997],{"class":161},[33,4569,308],{"class":117},[33,4571,4573,4576,4578,4580,4583],{"class":35,"line":4572},58,[33,4574,4575],{"class":139},"        itemSize",[33,4577,168],{"class":113},[33,4579,1979],{"class":117},[33,4581,4582],{"class":161},"40",[33,4584,308],{"class":117},[33,4586,4588,4591,4593],{"class":35,"line":4587},59,[33,4589,4590],{"class":139},"        width",[33,4592,168],{"class":113},[33,4594,2027],{"class":124},[33,4596,4598,4601,4603],{"class":35,"line":4597},60,[33,4599,4600],{"class":139},"        itemData",[33,4602,168],{"class":113},[33,4604,4605],{"class":117},"{filteredOrders}\n",[33,4607,4609],{"class":35,"line":4608},61,[33,4610,1820],{"class":117},[33,4612,4614,4617,4619,4621,4623,4625,4627,4629,4631],{"class":35,"line":4613},62,[33,4615,4616],{"class":117},"        {({ ",[33,4618,2058],{"class":182},[33,4620,186],{"class":117},[33,4622,2063],{"class":182},[33,4624,186],{"class":117},[33,4626,2419],{"class":182},[33,4628,2066],{"class":117},[33,4630,200],{"class":113},[33,4632,152],{"class":117},[33,4634,4636,4638,4640,4642,4644],{"class":35,"line":4635},63,[33,4637,1537],{"class":117},[33,4639,863],{"class":523},[33,4641,2089],{"class":139},[33,4643,168],{"class":113},[33,4645,2452],{"class":117},[33,4647,4649,4652],{"class":35,"line":4648},64,[33,4650,4651],{"class":117},"            \u003C",[33,4653,4654],{"class":161},"OrderRow\n",[33,4656,4658,4661,4663],{"class":35,"line":4657},65,[33,4659,4660],{"class":139},"              order",[33,4662,168],{"class":113},[33,4664,4665],{"class":117},"{data[index]}\n",[33,4667,4669,4672,4674],{"class":35,"line":4668},66,[33,4670,4671],{"class":139},"              onSelect",[33,4673,168],{"class":113},[33,4675,4676],{"class":117},"{handleSelect}\n",[33,4678,4680,4683,4685,4688,4690],{"class":35,"line":4679},67,[33,4681,4682],{"class":139},"              isSelected",[33,4684,168],{"class":113},[33,4686,4687],{"class":117},"{data[index].id ",[33,4689,227],{"class":113},[33,4691,4692],{"class":117}," selectedId}\n",[33,4694,4696],{"class":35,"line":4695},68,[33,4697,4698],{"class":117},"            \u002F>\n",[33,4700,4702,4705,4707],{"class":35,"line":4701},69,[33,4703,4704],{"class":117},"          \u003C\u002F",[33,4706,863],{"class":523},[33,4708,296],{"class":117},[33,4710,4712],{"class":35,"line":4711},70,[33,4713,4714],{"class":117},"        )}\n",[33,4716,4718,4720,4722],{"class":35,"line":4717},71,[33,4719,767],{"class":117},[33,4721,2126],{"class":161},[33,4723,296],{"class":117},[33,4725,4727,4729,4731],{"class":35,"line":4726},72,[33,4728,291],{"class":117},[33,4730,863],{"class":523},[33,4732,296],{"class":117},[33,4734,4736],{"class":35,"line":4735},73,[33,4737,302],{"class":117},[33,4739,4741],{"class":35,"line":4740},74,[33,4742,308],{"class":117},[33,4744,4746],{"class":35,"line":4745},75,[33,4747,101],{"emptyLinePlaceholder":100},[33,4749,4751],{"class":35,"line":4750},76,[33,4752,4753],{"class":39},"\u002F\u002F Filter input isolated — its state changes don't bubble up to Dashboard.\n",[33,4755,4757,4759,4762,4764,4766,4768,4770,4772,4774,4777,4779,4781],{"class":35,"line":4756},77,[33,4758,463],{"class":113},[33,4760,4761],{"class":161}," FilterInput",[33,4763,469],{"class":113},[33,4765,472],{"class":139},[33,4767,245],{"class":117},[33,4769,136],{"class":113},[33,4771,4761],{"class":139},[33,4773,481],{"class":117},[33,4775,4776],{"class":182},"value",[33,4778,186],{"class":117},[33,4780,3376],{"class":182},[33,4782,492],{"class":117},[33,4784,4786,4788,4790,4793,4795,4797,4800,4802,4804,4806,4808,4810,4813,4816,4819,4821,4824],{"class":35,"line":4785},78,[33,4787,149],{"class":113},[33,4789,736],{"class":117},[33,4791,4792],{"class":523},"input",[33,4794,3368],{"class":139},[33,4796,168],{"class":113},[33,4798,4799],{"class":117},"{value} ",[33,4801,3376],{"class":139},[33,4803,168],{"class":113},[33,4805,1979],{"class":117},[33,4807,3383],{"class":182},[33,4809,733],{"class":113},[33,4811,4812],{"class":139}," onChange",[33,4814,4815],{"class":117},"(e.target.value)} ",[33,4817,4818],{"class":139},"placeholder",[33,4820,168],{"class":113},[33,4822,4823],{"class":124},"\"Filter…\"",[33,4825,285],{"class":117},[33,4827,4829],{"class":35,"line":4828},79,[33,4830,584],{"class":117},[14,4832,4834],{"id":4833},"anti-pattern-performance-anti-patterns","Anti-Pattern — Performance Anti-Patterns",[19,4836,4838],{"filename":4837,"language":22},"anti_patterns.js",[24,4839,4841],{"className":26,"code":4840,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: React.memo on every component \"just in case\"\n\u002F\u002F The shallow comparison costs O(props) per render. For cheap components\n\u002F\u002F (a \u003Cspan> with text), the comparison is more expensive than the render\n\u002F\u002F it skips. Profile first — memo only expensive or frequently-re-rendered components.\n\n\u002F\u002F ANTI-PATTERN: useMemo for trivial computation\nconst fullName = useMemo(() => `${first} ${last}`, [first, last])\n\u002F\u002F The string concatenation is faster than useMemo's overhead (storing deps,\n\u002F\u002F comparing them). Use useMemo only for MEASURABLY expensive work.\n\n\u002F\u002F ANTI-PATTERN: useCallback on every function\nconst handleClick = useCallback(() => setCount(c => c + 1), [])\n\u002F\u002F If the function is passed to a NON-memoized child, useCallback adds\n\u002F\u002F overhead with zero benefit — the child re-renders regardless. Only use\n\u002F\u002F useCallback when the function is a dep of another hook OR passed to a\n\u002F\u002F memoized child.\n\n\u002F\u002F ANTI-PATTERN: index as key in a list that can reorder\n{items.map((item, i) => \u003CRow key={i} item={item} \u002F>)}\n\u002F\u002F Reordering items -> React keeps DOM node i but gives it item[j]'s data ->\n\u002F\u002F stale state, broken animations, input value misassociation.\n\n\u002F\u002F ANTI-PATTERN: virtualizing a short list\n\u002F\u002F 20 rows? DOM handles it fine. Virtualization adds complexity (absolute\n\u002F\u002F positioning, lost Ctrl+F, accessibility trade-offs). Only virtualize\n\u002F\u002F when profiling shows measurable jank — typically hundreds+ of rows.\n\n\u002F\u002F ANTI-PATTERN: React.memo with inline object\u002Farray props\n\u003CMemoizedChild config={{ theme: 'dark' }} \u002F>\n\u002F\u002F { theme: 'dark' } is a new object every render -> shallow compare fails ->\n\u002F\u002F memo does nothing. Lift the object to a constant or useMemo it.\nconst config = useMemo(() => ({ theme: 'dark' }), [])\n\u003CMemoizedChild config={config} \u002F>\n",[30,4842,4843,4848,4853,4858,4863,4867,4872,4904,4909,4914,4918,4923,4955,4960,4965,4970,4975,4979,4984,5020,5025,5030,5034,5039,5044,5049,5054,5058,5063,5084,5089,5094,5116],{"__ignoreMap":28},[33,4844,4845],{"class":35,"line":36},[33,4846,4847],{"class":39},"\u002F\u002F ANTI-PATTERN: React.memo on every component \"just in case\"\n",[33,4849,4850],{"class":35,"line":43},[33,4851,4852],{"class":39},"\u002F\u002F The shallow comparison costs O(props) per render. For cheap components\n",[33,4854,4855],{"class":35,"line":49},[33,4856,4857],{"class":39},"\u002F\u002F (a \u003Cspan> with text), the comparison is more expensive than the render\n",[33,4859,4860],{"class":35,"line":55},[33,4861,4862],{"class":39},"\u002F\u002F it skips. Profile first — memo only expensive or frequently-re-rendered components.\n",[33,4864,4865],{"class":35,"line":61},[33,4866,101],{"emptyLinePlaceholder":100},[33,4868,4869],{"class":35,"line":67},[33,4870,4871],{"class":39},"\u002F\u002F ANTI-PATTERN: useMemo for trivial computation\n",[33,4873,4874,4876,4879,4881,4883,4885,4887,4890,4893,4896,4899,4901],{"class":35,"line":73},[33,4875,463],{"class":113},[33,4877,4878],{"class":161}," fullName",[33,4880,469],{"class":113},[33,4882,1058],{"class":139},[33,4884,1061],{"class":117},[33,4886,200],{"class":113},[33,4888,4889],{"class":124}," `${",[33,4891,4892],{"class":117},"first",[33,4894,4895],{"class":124},"} ${",[33,4897,4898],{"class":117},"last",[33,4900,2822],{"class":124},[33,4902,4903],{"class":117},", [first, last])\n",[33,4905,4906],{"class":35,"line":79},[33,4907,4908],{"class":39},"\u002F\u002F The string concatenation is faster than useMemo's overhead (storing deps,\n",[33,4910,4911],{"class":35,"line":85},[33,4912,4913],{"class":39},"\u002F\u002F comparing them). Use useMemo only for MEASURABLY expensive work.\n",[33,4915,4916],{"class":35,"line":91},[33,4917,101],{"emptyLinePlaceholder":100},[33,4919,4920],{"class":35,"line":97},[33,4921,4922],{"class":39},"\u002F\u002F ANTI-PATTERN: useCallback on every function\n",[33,4924,4925,4927,4929,4931,4933,4935,4937,4940,4942,4944,4946,4948,4950,4952],{"class":35,"line":104},[33,4926,463],{"class":113},[33,4928,1251],{"class":161},[33,4930,469],{"class":113},[33,4932,674],{"class":139},[33,4934,1061],{"class":117},[33,4936,200],{"class":113},[33,4938,4939],{"class":139}," setCount",[33,4941,245],{"class":117},[33,4943,1271],{"class":182},[33,4945,733],{"class":113},[33,4947,1276],{"class":117},[33,4949,1141],{"class":113},[33,4951,1281],{"class":161},[33,4953,4954],{"class":117},"), [])\n",[33,4956,4957],{"class":35,"line":110},[33,4958,4959],{"class":39},"\u002F\u002F If the function is passed to a NON-memoized child, useCallback adds\n",[33,4961,4962],{"class":35,"line":128},[33,4963,4964],{"class":39},"\u002F\u002F overhead with zero benefit — the child re-renders regardless. Only use\n",[33,4966,4967],{"class":35,"line":133},[33,4968,4969],{"class":39},"\u002F\u002F useCallback when the function is a dep of another hook OR passed to a\n",[33,4971,4972],{"class":35,"line":146},[33,4973,4974],{"class":39},"\u002F\u002F memoized child.\n",[33,4976,4977],{"class":35,"line":155},[33,4978,101],{"emptyLinePlaceholder":100},[33,4980,4981],{"class":35,"line":206},[33,4982,4983],{"class":39},"\u002F\u002F ANTI-PATTERN: index as key in a list that can reorder\n",[33,4985,4986,4988,4990,4992,4994,4996,4999,5001,5003,5005,5007,5009,5011,5014,5016,5018],{"class":35,"line":212},[33,4987,2691],{"class":117},[33,4989,725],{"class":139},[33,4991,677],{"class":117},[33,4993,2698],{"class":182},[33,4995,186],{"class":117},[33,4997,4998],{"class":182},"i",[33,5000,197],{"class":117},[33,5002,200],{"class":113},[33,5004,736],{"class":117},[33,5006,2711],{"class":161},[33,5008,742],{"class":139},[33,5010,168],{"class":113},[33,5012,5013],{"class":117},"{i} ",[33,5015,2698],{"class":139},[33,5017,168],{"class":113},[33,5019,2725],{"class":117},[33,5021,5022],{"class":35,"line":218},[33,5023,5024],{"class":39},"\u002F\u002F Reordering items -> React keeps DOM node i but gives it item[j]'s data ->\n",[33,5026,5027],{"class":35,"line":236},[33,5028,5029],{"class":39},"\u002F\u002F stale state, broken animations, input value misassociation.\n",[33,5031,5032],{"class":35,"line":264},[33,5033,101],{"emptyLinePlaceholder":100},[33,5035,5036],{"class":35,"line":270},[33,5037,5038],{"class":39},"\u002F\u002F ANTI-PATTERN: virtualizing a short list\n",[33,5040,5041],{"class":35,"line":276},[33,5042,5043],{"class":39},"\u002F\u002F 20 rows? DOM handles it fine. Virtualization adds complexity (absolute\n",[33,5045,5046],{"class":35,"line":288},[33,5047,5048],{"class":39},"\u002F\u002F positioning, lost Ctrl+F, accessibility trade-offs). Only virtualize\n",[33,5050,5051],{"class":35,"line":299},[33,5052,5053],{"class":39},"\u002F\u002F when profiling shows measurable jank — typically hundreds+ of rows.\n",[33,5055,5056],{"class":35,"line":305},[33,5057,101],{"emptyLinePlaceholder":100},[33,5059,5060],{"class":35,"line":311},[33,5061,5062],{"class":39},"\u002F\u002F ANTI-PATTERN: React.memo with inline object\u002Farray props\n",[33,5064,5065,5068,5070,5073,5075,5078,5081],{"class":35,"line":316},[33,5066,5067],{"class":117},"\u003C",[33,5069,1304],{"class":161},[33,5071,5072],{"class":139}," config",[33,5074,168],{"class":113},[33,5076,5077],{"class":117},"{{ theme: ",[33,5079,5080],{"class":124},"'dark'",[33,5082,5083],{"class":117}," }} \u002F>\n",[33,5085,5086],{"class":35,"line":322},[33,5087,5088],{"class":39},"\u002F\u002F { theme: 'dark' } is a new object every render -> shallow compare fails ->\n",[33,5090,5091],{"class":35,"line":764},[33,5092,5093],{"class":39},"\u002F\u002F memo does nothing. Lift the object to a constant or useMemo it.\n",[33,5095,5096,5098,5100,5102,5104,5106,5108,5111,5113],{"class":35,"line":774},[33,5097,463],{"class":113},[33,5099,5072],{"class":161},[33,5101,469],{"class":113},[33,5103,1058],{"class":139},[33,5105,1061],{"class":117},[33,5107,200],{"class":113},[33,5109,5110],{"class":117}," ({ theme: ",[33,5112,5080],{"class":124},[33,5114,5115],{"class":117}," }), [])\n",[33,5117,5118,5120,5122,5124,5126],{"class":35,"line":783},[33,5119,5067],{"class":117},[33,5121,1304],{"class":161},[33,5123,5072],{"class":139},[33,5125,168],{"class":113},[33,5127,5128],{"class":117},"{config} \u002F>\n",[14,5130,5132],{"id":5131},"avoiding-unnecessary-re-renders-checklist","Avoiding Unnecessary Re-renders — Checklist",[19,5134,5136],{"filename":5135,"language":22},"re_render_checklist.js",[24,5137,5139],{"className":26,"code":5138,"language":22,"meta":28,"style":28},"\u002F\u002F CHECKLIST: run through this when a component re-renders unexpectedly.\n\n\u002F\u002F 1. IS THE RE-RENDER ACTUALLY A PROBLEM?\n\u002F\u002F    Profile it. A 0.1ms re-render of a \u003Cspan> is not worth optimizing.\n\n\u002F\u002F 2. DID A PARENT RE-RENDER?\n\u002F\u002F    If yes, and this component's props didn't change -> add React.memo.\n\u002F\u002F    But verify ALL props are stable first (step 3).\n\n\u002F\u002F 3. ARE ALL PROPS REFERENTIALLY STABLE?\n\u002F\u002F    - Functions -> wrapped in useCallback?\n\u002F\u002F    - Objects\u002Farrays -> wrapped in useMemo or lifted to constants?\n\u002F\u002F    - Inline JSX ({\u003CChild \u002F>}) -> new element every render, defeats memo.\n\u002F\u002F    One unstable prop defeats memo for the entire component.\n\n\u002F\u002F 4. CAN STATE MOVE DOWN?\n\u002F\u002F    If the re-rendering state is only used by one child subtree, move it\n\u002F\u002F    into that subtree. This eliminates the re-render at the source —\n\u002F\u002F    often better than memoization.\n\n\u002F\u002F 5. CAN CHILDREN BE PASSED AS PROPS?\n\u002F\u002F    If a wrapper owns frequently-changing state but wraps static children,\n\u002F\u002F    pass children as a prop from above — they won't re-render on wrapper's\n\u002F\u002F    state changes.\n\n\u002F\u002F 6. IS THE COMPUTATION GENUINELY EXPENSIVE?\n\u002F\u002F    useMemo only helps if the computation costs more than useMemo's own\n\u002F\u002F    overhead. Profile to confirm — large array transforms, complex\n\u002F\u002F    derivations, sorting\u002Ffiltering thousands of items qualify.\n\n\u002F\u002F 7. IS THE LIST LARGE ENOUGH TO VIRTUALIZE?\n\u002F\u002F    Hundreds+ rows with measured scroll jank -> react-window.\n\u002F\u002F    Small lists -> don't add the complexity.\n\n\u002F\u002F 8. CAN THE COMPONENT BE CODE-SPLIT?\n\u002F\u002F    If it's a route or conditionally-rendered feature, lazy() + Suspense\n\u002F\u002F    keeps its code out of the initial bundle entirely.\n",[30,5140,5141,5146,5150,5155,5160,5164,5169,5174,5179,5183,5188,5193,5198,5203,5208,5212,5217,5222,5227,5232,5236,5241,5246,5251,5256,5260,5265,5270,5275,5280,5284,5289,5294,5299,5303,5308,5313],{"__ignoreMap":28},[33,5142,5143],{"class":35,"line":36},[33,5144,5145],{"class":39},"\u002F\u002F CHECKLIST: run through this when a component re-renders unexpectedly.\n",[33,5147,5148],{"class":35,"line":43},[33,5149,101],{"emptyLinePlaceholder":100},[33,5151,5152],{"class":35,"line":49},[33,5153,5154],{"class":39},"\u002F\u002F 1. IS THE RE-RENDER ACTUALLY A PROBLEM?\n",[33,5156,5157],{"class":35,"line":55},[33,5158,5159],{"class":39},"\u002F\u002F    Profile it. A 0.1ms re-render of a \u003Cspan> is not worth optimizing.\n",[33,5161,5162],{"class":35,"line":61},[33,5163,101],{"emptyLinePlaceholder":100},[33,5165,5166],{"class":35,"line":67},[33,5167,5168],{"class":39},"\u002F\u002F 2. DID A PARENT RE-RENDER?\n",[33,5170,5171],{"class":35,"line":73},[33,5172,5173],{"class":39},"\u002F\u002F    If yes, and this component's props didn't change -> add React.memo.\n",[33,5175,5176],{"class":35,"line":79},[33,5177,5178],{"class":39},"\u002F\u002F    But verify ALL props are stable first (step 3).\n",[33,5180,5181],{"class":35,"line":85},[33,5182,101],{"emptyLinePlaceholder":100},[33,5184,5185],{"class":35,"line":91},[33,5186,5187],{"class":39},"\u002F\u002F 3. ARE ALL PROPS REFERENTIALLY STABLE?\n",[33,5189,5190],{"class":35,"line":97},[33,5191,5192],{"class":39},"\u002F\u002F    - Functions -> wrapped in useCallback?\n",[33,5194,5195],{"class":35,"line":104},[33,5196,5197],{"class":39},"\u002F\u002F    - Objects\u002Farrays -> wrapped in useMemo or lifted to constants?\n",[33,5199,5200],{"class":35,"line":110},[33,5201,5202],{"class":39},"\u002F\u002F    - Inline JSX ({\u003CChild \u002F>}) -> new element every render, defeats memo.\n",[33,5204,5205],{"class":35,"line":128},[33,5206,5207],{"class":39},"\u002F\u002F    One unstable prop defeats memo for the entire component.\n",[33,5209,5210],{"class":35,"line":133},[33,5211,101],{"emptyLinePlaceholder":100},[33,5213,5214],{"class":35,"line":146},[33,5215,5216],{"class":39},"\u002F\u002F 4. CAN STATE MOVE DOWN?\n",[33,5218,5219],{"class":35,"line":155},[33,5220,5221],{"class":39},"\u002F\u002F    If the re-rendering state is only used by one child subtree, move it\n",[33,5223,5224],{"class":35,"line":206},[33,5225,5226],{"class":39},"\u002F\u002F    into that subtree. This eliminates the re-render at the source —\n",[33,5228,5229],{"class":35,"line":212},[33,5230,5231],{"class":39},"\u002F\u002F    often better than memoization.\n",[33,5233,5234],{"class":35,"line":218},[33,5235,101],{"emptyLinePlaceholder":100},[33,5237,5238],{"class":35,"line":236},[33,5239,5240],{"class":39},"\u002F\u002F 5. CAN CHILDREN BE PASSED AS PROPS?\n",[33,5242,5243],{"class":35,"line":264},[33,5244,5245],{"class":39},"\u002F\u002F    If a wrapper owns frequently-changing state but wraps static children,\n",[33,5247,5248],{"class":35,"line":270},[33,5249,5250],{"class":39},"\u002F\u002F    pass children as a prop from above — they won't re-render on wrapper's\n",[33,5252,5253],{"class":35,"line":276},[33,5254,5255],{"class":39},"\u002F\u002F    state changes.\n",[33,5257,5258],{"class":35,"line":288},[33,5259,101],{"emptyLinePlaceholder":100},[33,5261,5262],{"class":35,"line":299},[33,5263,5264],{"class":39},"\u002F\u002F 6. IS THE COMPUTATION GENUINELY EXPENSIVE?\n",[33,5266,5267],{"class":35,"line":305},[33,5268,5269],{"class":39},"\u002F\u002F    useMemo only helps if the computation costs more than useMemo's own\n",[33,5271,5272],{"class":35,"line":311},[33,5273,5274],{"class":39},"\u002F\u002F    overhead. Profile to confirm — large array transforms, complex\n",[33,5276,5277],{"class":35,"line":316},[33,5278,5279],{"class":39},"\u002F\u002F    derivations, sorting\u002Ffiltering thousands of items qualify.\n",[33,5281,5282],{"class":35,"line":322},[33,5283,101],{"emptyLinePlaceholder":100},[33,5285,5286],{"class":35,"line":764},[33,5287,5288],{"class":39},"\u002F\u002F 7. IS THE LIST LARGE ENOUGH TO VIRTUALIZE?\n",[33,5290,5291],{"class":35,"line":774},[33,5292,5293],{"class":39},"\u002F\u002F    Hundreds+ rows with measured scroll jank -> react-window.\n",[33,5295,5296],{"class":35,"line":783},[33,5297,5298],{"class":39},"\u002F\u002F    Small lists -> don't add the complexity.\n",[33,5300,5301],{"class":35,"line":788},[33,5302,101],{"emptyLinePlaceholder":100},[33,5304,5305],{"class":35,"line":2226},[33,5306,5307],{"class":39},"\u002F\u002F 8. CAN THE COMPONENT BE CODE-SPLIT?\n",[33,5309,5310],{"class":35,"line":2234},[33,5311,5312],{"class":39},"\u002F\u002F    If it's a route or conditionally-rendered feature, lazy() + Suspense\n",[33,5314,5315],{"class":35,"line":2247},[33,5316,5317],{"class":39},"\u002F\u002F    keeps its code out of the initial bundle entirely.\n",[14,5319,5321],{"id":5320},"tips-tricks","💡 Tips & Tricks",[19,5323,5325],{"filename":5324,"language":22},"tips.js",[24,5326,5328],{"className":26,"code":5327,"language":22,"meta":28,"style":28},"\u002F\u002F [Performance] Always profile before optimizing — the Profiler's \"why did\n\u002F\u002F this render\" panel reveals the actual cause, which is rarely where intuition\n\u002F\u002F points. Optimizing the wrong component is the #1 performance mistake.\n\n\u002F\u002F [Idiom] Move state down to its narrowest necessary component BEFORE reaching\n\u002F\u002F for React.memo. It eliminates the re-render at the source with less code\n\u002F\u002F and zero comparison overhead.\n\n\u002F\u002F [Idiom] Apply code splitting at route boundaries first — highest leverage,\n\u002F\u002F lowest risk. Users only download code for pages they visit.\n\n\u002F\u002F [Performance] React.memo + useCallback + useMemo are a TRIPLE — memo on the\n\u002F\u002F child is useless without stable props from the parent. One un-memoized\n\u002F\u002F function\u002Fobject prop defeats the entire optimization.\n\n\u002F\u002F [Debug] React.memo's default shallow comparison means a new object\u002Farray\n\u002F\u002F every render (even with identical contents) always fails. Check for inline\n\u002F\u002F object\u002Farray\u002Ffunction props: { foo: 'bar' }, [1, 2, 3], () => handleClick().\n\n\u002F\u002F [Idiom] Use children-as-props to isolate re-renders: a wrapper component\n\u002F\u002F with frequently-changing state shouldn't construct its children itself —\n\u002F\u002F receive them as props from a parent that doesn't re-render as often.\n\n\u002F\u002F [Performance] Prefetch lazy chunks on hover\u002Ffocus — the download starts\n\u002F\u002F before the user clicks, so the chunk is ready by the time they do.\n\n\u002F\u002F [Portability] Virtualized lists break Ctrl+F and screen-reader \"read all\"\n\u002F\u002F navigation — off-screen rows genuinely don't exist in the DOM. Weigh this\n\u002F\u002F accessibility trade-off against the performance gain.\n",[30,5329,5330,5335,5340,5345,5349,5354,5359,5364,5368,5373,5378,5382,5387,5392,5397,5401,5406,5411,5416,5420,5425,5430,5435,5439,5444,5448,5452,5457,5462],{"__ignoreMap":28},[33,5331,5332],{"class":35,"line":36},[33,5333,5334],{"class":39},"\u002F\u002F [Performance] Always profile before optimizing — the Profiler's \"why did\n",[33,5336,5337],{"class":35,"line":43},[33,5338,5339],{"class":39},"\u002F\u002F this render\" panel reveals the actual cause, which is rarely where intuition\n",[33,5341,5342],{"class":35,"line":49},[33,5343,5344],{"class":39},"\u002F\u002F points. Optimizing the wrong component is the #1 performance mistake.\n",[33,5346,5347],{"class":35,"line":55},[33,5348,101],{"emptyLinePlaceholder":100},[33,5350,5351],{"class":35,"line":61},[33,5352,5353],{"class":39},"\u002F\u002F [Idiom] Move state down to its narrowest necessary component BEFORE reaching\n",[33,5355,5356],{"class":35,"line":67},[33,5357,5358],{"class":39},"\u002F\u002F for React.memo. It eliminates the re-render at the source with less code\n",[33,5360,5361],{"class":35,"line":73},[33,5362,5363],{"class":39},"\u002F\u002F and zero comparison overhead.\n",[33,5365,5366],{"class":35,"line":79},[33,5367,101],{"emptyLinePlaceholder":100},[33,5369,5370],{"class":35,"line":85},[33,5371,5372],{"class":39},"\u002F\u002F [Idiom] Apply code splitting at route boundaries first — highest leverage,\n",[33,5374,5375],{"class":35,"line":91},[33,5376,5377],{"class":39},"\u002F\u002F lowest risk. Users only download code for pages they visit.\n",[33,5379,5380],{"class":35,"line":97},[33,5381,101],{"emptyLinePlaceholder":100},[33,5383,5384],{"class":35,"line":104},[33,5385,5386],{"class":39},"\u002F\u002F [Performance] React.memo + useCallback + useMemo are a TRIPLE — memo on the\n",[33,5388,5389],{"class":35,"line":110},[33,5390,5391],{"class":39},"\u002F\u002F child is useless without stable props from the parent. One un-memoized\n",[33,5393,5394],{"class":35,"line":128},[33,5395,5396],{"class":39},"\u002F\u002F function\u002Fobject prop defeats the entire optimization.\n",[33,5398,5399],{"class":35,"line":133},[33,5400,101],{"emptyLinePlaceholder":100},[33,5402,5403],{"class":35,"line":146},[33,5404,5405],{"class":39},"\u002F\u002F [Debug] React.memo's default shallow comparison means a new object\u002Farray\n",[33,5407,5408],{"class":35,"line":155},[33,5409,5410],{"class":39},"\u002F\u002F every render (even with identical contents) always fails. Check for inline\n",[33,5412,5413],{"class":35,"line":206},[33,5414,5415],{"class":39},"\u002F\u002F object\u002Farray\u002Ffunction props: { foo: 'bar' }, [1, 2, 3], () => handleClick().\n",[33,5417,5418],{"class":35,"line":212},[33,5419,101],{"emptyLinePlaceholder":100},[33,5421,5422],{"class":35,"line":218},[33,5423,5424],{"class":39},"\u002F\u002F [Idiom] Use children-as-props to isolate re-renders: a wrapper component\n",[33,5426,5427],{"class":35,"line":236},[33,5428,5429],{"class":39},"\u002F\u002F with frequently-changing state shouldn't construct its children itself —\n",[33,5431,5432],{"class":35,"line":264},[33,5433,5434],{"class":39},"\u002F\u002F receive them as props from a parent that doesn't re-render as often.\n",[33,5436,5437],{"class":35,"line":270},[33,5438,101],{"emptyLinePlaceholder":100},[33,5440,5441],{"class":35,"line":276},[33,5442,5443],{"class":39},"\u002F\u002F [Performance] Prefetch lazy chunks on hover\u002Ffocus — the download starts\n",[33,5445,5446],{"class":35,"line":288},[33,5447,1641],{"class":39},[33,5449,5450],{"class":35,"line":299},[33,5451,101],{"emptyLinePlaceholder":100},[33,5453,5454],{"class":35,"line":305},[33,5455,5456],{"class":39},"\u002F\u002F [Portability] Virtualized lists break Ctrl+F and screen-reader \"read all\"\n",[33,5458,5459],{"class":35,"line":311},[33,5460,5461],{"class":39},"\u002F\u002F navigation — off-screen rows genuinely don't exist in the DOM. Weigh this\n",[33,5463,5464],{"class":35,"line":316},[33,5465,5466],{"class":39},"\u002F\u002F accessibility trade-off against the performance gain.\n",[14,5468,5470],{"id":5469},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,5472,5474],{"filename":5473,"language":22},"edge_cases.js",[24,5475,5477],{"className":26,"code":5476,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] React.memo compares props shallowly — { id: 1 } !== { id: 1 }\n\u002F\u002F (different references). A parent recreating object\u002Farray\u002Ffunction props\n\u002F\u002F inline defeats memo even though contents are identical. Use useMemo \u002F\n\u002F\u002F useCallback, or pass a custom comparison as memo's second argument.\n\n\u002F\u002F [Gotcha] React.memo on a component receiving `children` still re-renders\n\u002F\u002F when the parent re-renders — children is just another prop, and JSX\n\u002F\u002F literals create new element objects every render. Memo doesn't automatically\n\u002F\u002F make a wrapper immune to a parent's re-renders via children.\n\n\u002F\u002F [Gotcha] Overusing React.memo on cheap components adds NET overhead —\n\u002F\u002F the shallow comparison itself costs time. Wrapping every component \"just\n\u002F\u002F in case\" is a common overcorrection that measurably slows things down.\n\n\u002F\u002F [Gotcha] lazy()-loaded components without a \u003CSuspense> ancestor THROW at\n\u002F\u002F runtime, not merely warn — the first time the component's code needs to\n\u002F\u002F load, the app crashes. Every lazy() needs a Suspense somewhere above it.\n\n\u002F\u002F [Gotcha] Virtualized lists (react-window) break native Ctrl+F \u002F Cmd+F\n\u002F\u002F in-page search and screen-reader \"read whole list\" — off-screen rows\n\u002F\u002F genuinely don't exist in the DOM. This is a real accessibility trade-off.\n\n\u002F\u002F [Gotcha] react-window's FixedSizeList requires the `style` prop to be\n\u002F\u002F applied to the row's outer element — it uses absolute positioning with\n\u002F\u002F a computed `top`. Overriding or omitting `style` breaks scroll positioning.\n\n\u002F\u002F [Gotcha] Using index as key in a list that can reorder (sort, filter,\n\u002F\u002F insert at top) causes React to reuse DOM nodes with WRONG data — stale\n\u002F\u002F state, broken animations, input value misassociation. Use stable unique IDs.\n\n\u002F\u002F [Gotcha] useMemo\u002FuseCallback have their own overhead (storing previous\n\u002F\u002F deps, comparing them every render). For trivial work (a + b, string concat),\n\u002F\u002F they cost more than they save. Reserve for CONFIRMED expensive computations.\n\n\u002F\u002F [Gotcha] Structural sharing: `{ ...p }` on every item in a map creates\n\u002F\u002F new references for ALL items, causing all memoized rows to re-render.\n\u002F\u002F Only create a new reference for the CHANGED item: `p.id === id ? updated : p`.\n",[30,5478,5479,5484,5489,5494,5499,5503,5508,5513,5518,5523,5527,5532,5537,5542,5546,5551,5556,5561,5565,5570,5575,5580,5584,5589,5594,5599,5603,5608,5613,5618,5622,5627,5632,5637,5641,5646,5651],{"__ignoreMap":28},[33,5480,5481],{"class":35,"line":36},[33,5482,5483],{"class":39},"\u002F\u002F [Gotcha] React.memo compares props shallowly — { id: 1 } !== { id: 1 }\n",[33,5485,5486],{"class":35,"line":43},[33,5487,5488],{"class":39},"\u002F\u002F (different references). A parent recreating object\u002Farray\u002Ffunction props\n",[33,5490,5491],{"class":35,"line":49},[33,5492,5493],{"class":39},"\u002F\u002F inline defeats memo even though contents are identical. Use useMemo \u002F\n",[33,5495,5496],{"class":35,"line":55},[33,5497,5498],{"class":39},"\u002F\u002F useCallback, or pass a custom comparison as memo's second argument.\n",[33,5500,5501],{"class":35,"line":61},[33,5502,101],{"emptyLinePlaceholder":100},[33,5504,5505],{"class":35,"line":67},[33,5506,5507],{"class":39},"\u002F\u002F [Gotcha] React.memo on a component receiving `children` still re-renders\n",[33,5509,5510],{"class":35,"line":73},[33,5511,5512],{"class":39},"\u002F\u002F when the parent re-renders — children is just another prop, and JSX\n",[33,5514,5515],{"class":35,"line":79},[33,5516,5517],{"class":39},"\u002F\u002F literals create new element objects every render. Memo doesn't automatically\n",[33,5519,5520],{"class":35,"line":85},[33,5521,5522],{"class":39},"\u002F\u002F make a wrapper immune to a parent's re-renders via children.\n",[33,5524,5525],{"class":35,"line":91},[33,5526,101],{"emptyLinePlaceholder":100},[33,5528,5529],{"class":35,"line":97},[33,5530,5531],{"class":39},"\u002F\u002F [Gotcha] Overusing React.memo on cheap components adds NET overhead —\n",[33,5533,5534],{"class":35,"line":104},[33,5535,5536],{"class":39},"\u002F\u002F the shallow comparison itself costs time. Wrapping every component \"just\n",[33,5538,5539],{"class":35,"line":110},[33,5540,5541],{"class":39},"\u002F\u002F in case\" is a common overcorrection that measurably slows things down.\n",[33,5543,5544],{"class":35,"line":128},[33,5545,101],{"emptyLinePlaceholder":100},[33,5547,5548],{"class":35,"line":133},[33,5549,5550],{"class":39},"\u002F\u002F [Gotcha] lazy()-loaded components without a \u003CSuspense> ancestor THROW at\n",[33,5552,5553],{"class":35,"line":146},[33,5554,5555],{"class":39},"\u002F\u002F runtime, not merely warn — the first time the component's code needs to\n",[33,5557,5558],{"class":35,"line":155},[33,5559,5560],{"class":39},"\u002F\u002F load, the app crashes. Every lazy() needs a Suspense somewhere above it.\n",[33,5562,5563],{"class":35,"line":206},[33,5564,101],{"emptyLinePlaceholder":100},[33,5566,5567],{"class":35,"line":212},[33,5568,5569],{"class":39},"\u002F\u002F [Gotcha] Virtualized lists (react-window) break native Ctrl+F \u002F Cmd+F\n",[33,5571,5572],{"class":35,"line":218},[33,5573,5574],{"class":39},"\u002F\u002F in-page search and screen-reader \"read whole list\" — off-screen rows\n",[33,5576,5577],{"class":35,"line":236},[33,5578,5579],{"class":39},"\u002F\u002F genuinely don't exist in the DOM. This is a real accessibility trade-off.\n",[33,5581,5582],{"class":35,"line":264},[33,5583,101],{"emptyLinePlaceholder":100},[33,5585,5586],{"class":35,"line":270},[33,5587,5588],{"class":39},"\u002F\u002F [Gotcha] react-window's FixedSizeList requires the `style` prop to be\n",[33,5590,5591],{"class":35,"line":276},[33,5592,5593],{"class":39},"\u002F\u002F applied to the row's outer element — it uses absolute positioning with\n",[33,5595,5596],{"class":35,"line":288},[33,5597,5598],{"class":39},"\u002F\u002F a computed `top`. Overriding or omitting `style` breaks scroll positioning.\n",[33,5600,5601],{"class":35,"line":299},[33,5602,101],{"emptyLinePlaceholder":100},[33,5604,5605],{"class":35,"line":305},[33,5606,5607],{"class":39},"\u002F\u002F [Gotcha] Using index as key in a list that can reorder (sort, filter,\n",[33,5609,5610],{"class":35,"line":311},[33,5611,5612],{"class":39},"\u002F\u002F insert at top) causes React to reuse DOM nodes with WRONG data — stale\n",[33,5614,5615],{"class":35,"line":316},[33,5616,5617],{"class":39},"\u002F\u002F state, broken animations, input value misassociation. Use stable unique IDs.\n",[33,5619,5620],{"class":35,"line":322},[33,5621,101],{"emptyLinePlaceholder":100},[33,5623,5624],{"class":35,"line":764},[33,5625,5626],{"class":39},"\u002F\u002F [Gotcha] useMemo\u002FuseCallback have their own overhead (storing previous\n",[33,5628,5629],{"class":35,"line":774},[33,5630,5631],{"class":39},"\u002F\u002F deps, comparing them every render). For trivial work (a + b, string concat),\n",[33,5633,5634],{"class":35,"line":783},[33,5635,5636],{"class":39},"\u002F\u002F they cost more than they save. Reserve for CONFIRMED expensive computations.\n",[33,5638,5639],{"class":35,"line":788},[33,5640,101],{"emptyLinePlaceholder":100},[33,5642,5643],{"class":35,"line":2226},[33,5644,5645],{"class":39},"\u002F\u002F [Gotcha] Structural sharing: `{ ...p }` on every item in a map creates\n",[33,5647,5648],{"class":35,"line":2234},[33,5649,5650],{"class":39},"\u002F\u002F new references for ALL items, causing all memoized rows to re-render.\n",[33,5652,5653],{"class":35,"line":2247},[33,5654,5655],{"class":39},"\u002F\u002F Only create a new reference for the CHANGED item: `p.id === id ? updated : p`.\n",[14,5657,5659],{"id":5658},"spot-the-bug","🧠 Spot the Bug",[730,5661,5662,5663,5666,5667,5670],{},"A ",[30,5664,5665],{},"ProductList"," wraps each row in ",[30,5668,5669],{},"React.memo",", expecting scrolling\u002Ffiltering to skip re-rendering unaffected rows. The Profiler shows every single row still re-rendering on every keystroke in an unrelated search box.",[19,5672,5674],{"filename":5673,"language":22},"spot_the_bug.js",[24,5675,5677],{"className":26,"code":5676,"language":22,"meta":28,"style":28},"const ProductRow = React.memo(function ProductRow({ product, onFavorite }) {\n  return (\n    \u003Cdiv>\n      {product.name}\n      \u003Cbutton onClick={() => onFavorite(product.id)}>♥\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )\n})\n\nfunction ProductList({ products }) {\n  const [favorites, setFavorites] = useState(new Set())\n\n  function handleFavorite(id) {\n    setFavorites(prev => new Set(prev).add(id))\n  }\n\n  return (\n    \u003Cdiv>\n      {products.map(product => (\n        \u003CProductRow key={product.id} product={product} onFavorite={handleFavorite} \u002F>\n      ))}\n    \u003C\u002Fdiv>\n  )\n}\n",[30,5678,5679,5710,5716,5724,5729,5753,5761,5765,5769,5773,5785,5816,5820,5833,5859,5863,5867,5873,5881,5896,5923,5928,5936,5940],{"__ignoreMap":28},[33,5680,5681,5683,5685,5687,5690,5693,5695,5697,5699,5701,5703,5705,5708],{"class":35,"line":36},[33,5682,463],{"class":113},[33,5684,466],{"class":161},[33,5686,469],{"class":113},[33,5688,5689],{"class":117}," React.",[33,5691,5692],{"class":139},"memo",[33,5694,245],{"class":117},[33,5696,136],{"class":113},[33,5698,466],{"class":139},[33,5700,481],{"class":117},[33,5702,484],{"class":182},[33,5704,186],{"class":117},[33,5706,5707],{"class":182},"onFavorite",[33,5709,492],{"class":117},[33,5711,5712,5714],{"class":35,"line":43},[33,5713,149],{"class":113},[33,5715,152],{"class":117},[33,5717,5718,5720,5722],{"class":35,"line":49},[33,5719,158],{"class":117},[33,5721,863],{"class":523},[33,5723,296],{"class":117},[33,5725,5726],{"class":35,"line":55},[33,5727,5728],{"class":117},"      {product.name}\n",[33,5730,5731,5733,5735,5737,5739,5741,5743,5746,5749,5751],{"class":35,"line":61},[33,5732,279],{"class":117},[33,5734,1832],{"class":523},[33,5736,527],{"class":139},[33,5738,168],{"class":113},[33,5740,532],{"class":117},[33,5742,200],{"class":113},[33,5744,5745],{"class":139}," onFavorite",[33,5747,5748],{"class":117},"(product.id)}>♥\u003C\u002F",[33,5750,1832],{"class":523},[33,5752,296],{"class":117},[33,5754,5755,5757,5759],{"class":35,"line":67},[33,5756,291],{"class":117},[33,5758,863],{"class":523},[33,5760,296],{"class":117},[33,5762,5763],{"class":35,"line":73},[33,5764,302],{"class":117},[33,5766,5767],{"class":35,"line":79},[33,5768,584],{"class":117},[33,5770,5771],{"class":35,"line":85},[33,5772,101],{"emptyLinePlaceholder":100},[33,5774,5775,5777,5779,5781,5783],{"class":35,"line":91},[33,5776,136],{"class":113},[33,5778,1950],{"class":139},[33,5780,481],{"class":117},[33,5782,610],{"class":182},[33,5784,492],{"class":117},[33,5786,5787,5789,5791,5794,5796,5799,5801,5803,5805,5807,5810,5813],{"class":35,"line":97},[33,5788,617],{"class":113},[33,5790,620],{"class":117},[33,5792,5793],{"class":161},"favorites",[33,5795,186],{"class":117},[33,5797,5798],{"class":161},"setFavorites",[33,5800,631],{"class":117},[33,5802,168],{"class":113},[33,5804,636],{"class":139},[33,5806,245],{"class":117},[33,5808,5809],{"class":113},"new",[33,5811,5812],{"class":139}," Set",[33,5814,5815],{"class":117},"())\n",[33,5817,5818],{"class":35,"line":104},[33,5819,101],{"emptyLinePlaceholder":100},[33,5821,5822,5824,5827,5829,5831],{"class":35,"line":110},[33,5823,1711],{"class":113},[33,5825,5826],{"class":139}," handleFavorite",[33,5828,245],{"class":117},[33,5830,183],{"class":182},[33,5832,233],{"class":117},[33,5834,5835,5838,5840,5843,5845,5848,5850,5853,5856],{"class":35,"line":128},[33,5836,5837],{"class":139},"    setFavorites",[33,5839,245],{"class":117},[33,5841,5842],{"class":182},"prev",[33,5844,733],{"class":113},[33,5846,5847],{"class":113}," new",[33,5849,5812],{"class":139},[33,5851,5852],{"class":117},"(prev).",[33,5854,5855],{"class":139},"add",[33,5857,5858],{"class":117},"(id))\n",[33,5860,5861],{"class":35,"line":133},[33,5862,1742],{"class":117},[33,5864,5865],{"class":35,"line":146},[33,5866,101],{"emptyLinePlaceholder":100},[33,5868,5869,5871],{"class":35,"line":155},[33,5870,149],{"class":113},[33,5872,152],{"class":117},[33,5874,5875,5877,5879],{"class":35,"line":206},[33,5876,158],{"class":117},[33,5878,863],{"class":523},[33,5880,296],{"class":117},[33,5882,5883,5886,5888,5890,5892,5894],{"class":35,"line":212},[33,5884,5885],{"class":117},"      {products.",[33,5887,725],{"class":139},[33,5889,245],{"class":117},[33,5891,484],{"class":182},[33,5893,733],{"class":113},[33,5895,152],{"class":117},[33,5897,5898,5900,5902,5904,5906,5909,5911,5913,5916,5918,5920],{"class":35,"line":218},[33,5899,1515],{"class":117},[33,5901,739],{"class":161},[33,5903,742],{"class":139},[33,5905,168],{"class":113},[33,5907,5908],{"class":117},"{product.id} ",[33,5910,484],{"class":139},[33,5912,168],{"class":113},[33,5914,5915],{"class":117},"{product} ",[33,5917,5707],{"class":139},[33,5919,168],{"class":113},[33,5921,5922],{"class":117},"{handleFavorite} \u002F>\n",[33,5924,5925],{"class":35,"line":236},[33,5926,5927],{"class":117},"      ))}\n",[33,5929,5930,5932,5934],{"class":35,"line":264},[33,5931,291],{"class":117},[33,5933,863],{"class":523},[33,5935,296],{"class":117},[33,5937,5938],{"class":35,"line":270},[33,5939,302],{"class":117},[33,5941,5942],{"class":35,"line":276},[33,5943,308],{"class":117},[5945,5946,5947,5951,5982,5994,6048,6063],"details",{},[5948,5949,5950],"summary",{},"Answer",[730,5952,5953,5956,5957,5959,5960,5963,5964,5966,5967,5969,5970,5972,5973,5975,5976,5978,5979,5981],{},[30,5954,5955],{},"handleFavorite"," is defined fresh inside ",[30,5958,5665],{},"'s function body on every render, with no ",[30,5961,5962],{},"useCallback"," wrapping it. Every time ",[30,5965,5665],{}," re-renders (including when an unrelated search box elsewhere causes a re-render further up the tree), a brand-new ",[30,5968,5955],{}," function reference is created and passed as ",[30,5971,5707],{}," to every ",[30,5974,739],{},". ",[30,5977,5669],{},"'s shallow comparison sees ",[30,5980,5707],{}," as \"changed\" (new function reference, every time) for every row, on every render — defeating the memoization entirely.",[730,5983,5984,5988,5989,5991,5992,2908],{},[5985,5986,5987],"strong",{},"Fix",": wrap ",[30,5990,5955],{}," in ",[30,5993,5962],{},[24,5995,5997],{"className":26,"code":5996,"language":22,"meta":28,"style":28},"const handleFavorite = useCallback((id) => {\n  setFavorites(prev => new Set(prev).add(id))\n}, [])  \u002F\u002F setFavorites is guaranteed stable; empty deps = stable forever\n",[30,5998,5999,6019,6040],{"__ignoreMap":28},[33,6000,6001,6003,6005,6007,6009,6011,6013,6015,6017],{"class":35,"line":36},[33,6002,463],{"class":113},[33,6004,5826],{"class":161},[33,6006,469],{"class":113},[33,6008,674],{"class":139},[33,6010,677],{"class":117},[33,6012,183],{"class":182},[33,6014,197],{"class":117},[33,6016,200],{"class":113},[33,6018,203],{"class":117},[33,6020,6021,6024,6026,6028,6030,6032,6034,6036,6038],{"class":35,"line":43},[33,6022,6023],{"class":139},"  setFavorites",[33,6025,245],{"class":117},[33,6027,5842],{"class":182},[33,6029,733],{"class":113},[33,6031,5847],{"class":113},[33,6033,5812],{"class":139},[33,6035,5852],{"class":117},[33,6037,5855],{"class":139},[33,6039,5858],{"class":117},[33,6041,6042,6045],{"class":35,"line":49},[33,6043,6044],{"class":117},"}, [])  ",[33,6046,6047],{"class":39},"\u002F\u002F setFavorites is guaranteed stable; empty deps = stable forever\n",[730,6049,6050,6051,6053,6054,6056,6057,6059,6060,6062],{},"Now ",[30,6052,5955],{}," has the same reference across renders -> ",[30,6055,5669],{}," sees ",[30,6058,5707],{}," as unchanged -> rows skip re-rendering when only ",[30,6061,484],{}," is unchanged.",[730,6064,6065,6068,6069,6071,6072,6076],{},[5985,6066,6067],{},"The lesson",": ",[30,6070,5669],{}," only helps if ",[6073,6074,6075],"em",{},"every"," prop is referentially stable. A single un-memoized function or object prop is enough to defeat it completely.",[14,6078,6080],{"id":6079},"key-takeaways","Key Takeaways",[19,6082,6084],{"filename":6083,"language":22},"key_takeaways.js",[24,6085,6087],{"className":26,"code":6086,"language":22,"meta":28,"style":28},"\u002F\u002F 1. PROFILE BEFORE OPTIMIZING. The DevTools Profiler's \"why did this render\"\n\u002F\u002F    panel turns guesses into measurements. Optimizing the wrong component is\n\u002F\u002F    the #1 performance mistake — intuition is frequently wrong.\n\n\u002F\u002F 2. React.memo skips a re-render when props are shallow-equal — but is\n\u002F\u002F    completely defeated by even ONE un-memoized function\u002Fobject\u002Farray prop\n\u002F\u002F    recreated fresh each render. Memo + useCallback + useMemo are a triple.\n\n\u002F\u002F 3. Code splitting with lazy() + Suspense at route boundaries = highest-\n\u002F\u002F    leverage optimization. Users only download code for pages they visit.\n\u002F\u002F    Prefetch on hover\u002Ffocus to make it instant.\n\n\u002F\u002F 4. Virtualization (react-window) renders only visible rows — DOM node\n\u002F\u002F    count stays flat regardless of dataset size. Worth its complexity\n\u002F\u002F    (and accessibility trade-offs) only for lists large enough to cause\n\u002F\u002F    measured jank — typically hundreds+ of rows.\n\n\u002F\u002F 5. Moving state down to the narrowest component that needs it eliminates\n\u002F\u002F    re-renders at the source — often better than memoization, with less\n\u002F\u002F    code and zero comparison overhead. Children-as-props isolates further.\n\n\u002F\u002F 6. useMemo earns its overhead only for CONFIRMED expensive computations\n\u002F\u002F    (large array transforms, complex derivations). Wrapping trivial work\n\u002F\u002F    (a + b) costs more than it saves. Profile to confirm.\n\n\u002F\u002F 7. Bundle analysis (webpack-bundle-analyzer, source-map-explorer) reveals\n\u002F\u002F    what's actually shipping — large deps, duplicate versions, tree-shaking\n\u002F\u002F    failures, server-only code leaking to the client. Audit regularly.\n",[30,6088,6089,6094,6099,6104,6108,6113,6118,6123,6127,6132,6137,6142,6146,6151,6156,6161,6166,6170,6175,6180,6185,6189,6194,6199,6204,6208,6213,6218],{"__ignoreMap":28},[33,6090,6091],{"class":35,"line":36},[33,6092,6093],{"class":39},"\u002F\u002F 1. PROFILE BEFORE OPTIMIZING. The DevTools Profiler's \"why did this render\"\n",[33,6095,6096],{"class":35,"line":43},[33,6097,6098],{"class":39},"\u002F\u002F    panel turns guesses into measurements. Optimizing the wrong component is\n",[33,6100,6101],{"class":35,"line":49},[33,6102,6103],{"class":39},"\u002F\u002F    the #1 performance mistake — intuition is frequently wrong.\n",[33,6105,6106],{"class":35,"line":55},[33,6107,101],{"emptyLinePlaceholder":100},[33,6109,6110],{"class":35,"line":61},[33,6111,6112],{"class":39},"\u002F\u002F 2. React.memo skips a re-render when props are shallow-equal — but is\n",[33,6114,6115],{"class":35,"line":67},[33,6116,6117],{"class":39},"\u002F\u002F    completely defeated by even ONE un-memoized function\u002Fobject\u002Farray prop\n",[33,6119,6120],{"class":35,"line":73},[33,6121,6122],{"class":39},"\u002F\u002F    recreated fresh each render. Memo + useCallback + useMemo are a triple.\n",[33,6124,6125],{"class":35,"line":79},[33,6126,101],{"emptyLinePlaceholder":100},[33,6128,6129],{"class":35,"line":85},[33,6130,6131],{"class":39},"\u002F\u002F 3. Code splitting with lazy() + Suspense at route boundaries = highest-\n",[33,6133,6134],{"class":35,"line":91},[33,6135,6136],{"class":39},"\u002F\u002F    leverage optimization. Users only download code for pages they visit.\n",[33,6138,6139],{"class":35,"line":97},[33,6140,6141],{"class":39},"\u002F\u002F    Prefetch on hover\u002Ffocus to make it instant.\n",[33,6143,6144],{"class":35,"line":104},[33,6145,101],{"emptyLinePlaceholder":100},[33,6147,6148],{"class":35,"line":110},[33,6149,6150],{"class":39},"\u002F\u002F 4. Virtualization (react-window) renders only visible rows — DOM node\n",[33,6152,6153],{"class":35,"line":128},[33,6154,6155],{"class":39},"\u002F\u002F    count stays flat regardless of dataset size. Worth its complexity\n",[33,6157,6158],{"class":35,"line":133},[33,6159,6160],{"class":39},"\u002F\u002F    (and accessibility trade-offs) only for lists large enough to cause\n",[33,6162,6163],{"class":35,"line":146},[33,6164,6165],{"class":39},"\u002F\u002F    measured jank — typically hundreds+ of rows.\n",[33,6167,6168],{"class":35,"line":155},[33,6169,101],{"emptyLinePlaceholder":100},[33,6171,6172],{"class":35,"line":206},[33,6173,6174],{"class":39},"\u002F\u002F 5. Moving state down to the narrowest component that needs it eliminates\n",[33,6176,6177],{"class":35,"line":212},[33,6178,6179],{"class":39},"\u002F\u002F    re-renders at the source — often better than memoization, with less\n",[33,6181,6182],{"class":35,"line":218},[33,6183,6184],{"class":39},"\u002F\u002F    code and zero comparison overhead. Children-as-props isolates further.\n",[33,6186,6187],{"class":35,"line":236},[33,6188,101],{"emptyLinePlaceholder":100},[33,6190,6191],{"class":35,"line":264},[33,6192,6193],{"class":39},"\u002F\u002F 6. useMemo earns its overhead only for CONFIRMED expensive computations\n",[33,6195,6196],{"class":35,"line":270},[33,6197,6198],{"class":39},"\u002F\u002F    (large array transforms, complex derivations). Wrapping trivial work\n",[33,6200,6201],{"class":35,"line":276},[33,6202,6203],{"class":39},"\u002F\u002F    (a + b) costs more than it saves. Profile to confirm.\n",[33,6205,6206],{"class":35,"line":288},[33,6207,101],{"emptyLinePlaceholder":100},[33,6209,6210],{"class":35,"line":299},[33,6211,6212],{"class":39},"\u002F\u002F 7. Bundle analysis (webpack-bundle-analyzer, source-map-explorer) reveals\n",[33,6214,6215],{"class":35,"line":305},[33,6216,6217],{"class":39},"\u002F\u002F    what's actually shipping — large deps, duplicate versions, tree-shaking\n",[33,6219,6220],{"class":35,"line":311},[33,6221,6222],{"class":39},"\u002F\u002F    failures, server-only code leaking to the client. Audit regularly.\n",[2063,6224,6225],{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":28,"searchDepth":43,"depth":43,"links":6227},[6228,6231,6234,6235,6238,6241,6242,6245,6248,6249,6250,6251,6252,6253,6254],{"id":16,"depth":43,"text":17,"children":6229},[6230],{"id":329,"depth":49,"text":330},{"id":415,"depth":43,"text":416,"children":6232},[6233],{"id":793,"depth":49,"text":794},{"id":994,"depth":43,"text":995},{"id":1342,"depth":43,"text":1343,"children":6236},[6237],{"id":1608,"depth":49,"text":1609},{"id":1899,"depth":43,"text":1900,"children":6239},[6240],{"id":2344,"depth":49,"text":2345},{"id":2648,"depth":43,"text":2649},{"id":2962,"depth":43,"text":2963,"children":6243},[6244],{"id":3125,"depth":49,"text":3126},{"id":3247,"depth":43,"text":3248,"children":6246},[6247],{"id":3570,"depth":49,"text":3571},{"id":3875,"depth":43,"text":3876},{"id":4833,"depth":43,"text":4834},{"id":5131,"depth":43,"text":5132},{"id":5320,"depth":43,"text":5321},{"id":5469,"depth":43,"text":5470},{"id":5658,"depth":43,"text":5659},{"id":6079,"depth":43,"text":6080},"React DevTools Profiler, React.memo + useMemo + useCallback, code splitting with React.lazy + Suspense, virtualization with react-window, list optimization, bundle analysis, and a re-render elimination checklist. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F20-performance-optimization",{"title":5,"description":6255},"react\u002F20-performance-optimization","6AeqxH8f7d-lEb215Os1CIAUWjdMEyR8qRwgfa0_TQw",1789924652477]