[{"data":1,"prerenderedAt":1611},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-usememo-usecallback-patterns-guide":3},{"id":4,"title":5,"body":6,"description":1596,"difficulty":1597,"extension":1598,"framework":1599,"frameworkSlug":1600,"meta":1601,"navigation":154,"order":126,"path":1602,"qaPath":1603,"seo":1604,"stem":1605,"subtopic":1606,"topic":1607,"topicSlug":1608,"updated":1609,"__hash__":1610},"blog\u002Fblog\u002Freact-usememo-usecallback-patterns-guide.md","React useMemo and useCallback Patterns — A Complete Guide",{"type":7,"value":8,"toc":1585},"minimark",[9,14,26,29,33,40,62,75,212,216,227,230,258,269,273,282,345,364,471,480,484,487,495,511,625,628,632,638,813,820,824,837,1101,1124,1128,1135,1327,1335,1339,1348,1437,1449,1514,1520,1578,1581],[10,11,13],"h2",{"id":12},"why-interviewers-ask-about-usememo-and-usecallback","Why interviewers ask about useMemo and useCallback",[15,16,17,21,22,25],"p",{},[18,19,20],"strong",{},"useMemo"," and ",[18,23,24],{},"useCallback"," are React's two memoisation hooks, and they\ncome up in almost every mid-to-senior React interview because they sit at the\nintersection of performance, correctness, and idiomatic code style. Getting\nthem wrong in both directions — over-using them and under-using them — signals\na shallow understanding of the rendering model.",[15,27,28],{},"The goal of this guide is to give you a mental model that lets you answer not\njust \"what does it do?\" but \"when do I reach for it and why?\"",[10,30,32],{"id":31},"usememo-vs-usecallback-the-core-distinction","useMemo vs useCallback — the core distinction",[15,34,35,36,39],{},"Both hooks cache a value between renders and invalidate it when dependencies\nchange. The only difference is ",[18,37,38],{},"what they cache",":",[41,42,43,54],"ul",{},[44,45,46,49,50,53],"li",{},[47,48,20],"code",{}," caches a ",[18,51,52],{},"computed value"," (an object, array, number, string…)",[44,55,56,49,58,61],{},[47,57,24],{},[18,59,60],{},"function definition"," (its reference)",[15,63,64,67,68,71,72,74],{},[47,65,66],{},"useCallback(fn, deps)"," is exactly equivalent to ",[47,69,70],{},"useMemo(() => fn, deps)",".\nReact ships ",[47,73,24],{}," as a convenience because caching functions is such a\ncommon need.",[76,77,82],"pre",{"className":78,"code":79,"language":80,"meta":81,"style":81},"language-jsx shiki shiki-themes github-light github-dark","\u002F\u002F These are identical in behaviour:\nconst memoFn = useCallback(() => doWork(id), [id])\nconst memoFn = useMemo(() => () => doWork(id), [id])\n\n\u002F\u002F useMemo for a value\nconst sortedItems = useMemo(\n  () => [...items].sort(byDate),\n  [items]\n)\n","jsx","",[47,83,84,93,124,149,156,162,177,200,206],{"__ignoreMap":81},[85,86,89],"span",{"class":87,"line":88},"line",1,[85,90,92],{"class":91},"sJ8bj","\u002F\u002F These are identical in behaviour:\n",[85,94,96,100,104,107,111,115,118,121],{"class":87,"line":95},2,[85,97,99],{"class":98},"szBVR","const",[85,101,103],{"class":102},"sj4cs"," memoFn",[85,105,106],{"class":98}," =",[85,108,110],{"class":109},"sScJk"," useCallback",[85,112,114],{"class":113},"sVt8B","(() ",[85,116,117],{"class":98},"=>",[85,119,120],{"class":109}," doWork",[85,122,123],{"class":113},"(id), [id])\n",[85,125,127,129,131,133,136,138,140,143,145,147],{"class":87,"line":126},3,[85,128,99],{"class":98},[85,130,103],{"class":102},[85,132,106],{"class":98},[85,134,135],{"class":109}," useMemo",[85,137,114],{"class":113},[85,139,117],{"class":98},[85,141,142],{"class":113}," () ",[85,144,117],{"class":98},[85,146,120],{"class":109},[85,148,123],{"class":113},[85,150,152],{"class":87,"line":151},4,[85,153,155],{"emptyLinePlaceholder":154},true,"\n",[85,157,159],{"class":87,"line":158},5,[85,160,161],{"class":91},"\u002F\u002F useMemo for a value\n",[85,163,165,167,170,172,174],{"class":87,"line":164},6,[85,166,99],{"class":98},[85,168,169],{"class":102}," sortedItems",[85,171,106],{"class":98},[85,173,135],{"class":109},[85,175,176],{"class":113},"(\n",[85,178,180,183,185,188,191,194,197],{"class":87,"line":179},7,[85,181,182],{"class":113},"  () ",[85,184,117],{"class":98},[85,186,187],{"class":113}," [",[85,189,190],{"class":98},"...",[85,192,193],{"class":113},"items].",[85,195,196],{"class":109},"sort",[85,198,199],{"class":113},"(byDate),\n",[85,201,203],{"class":87,"line":202},8,[85,204,205],{"class":113},"  [items]\n",[85,207,209],{"class":87,"line":208},9,[85,210,211],{"class":113},")\n",[10,213,215],{"id":214},"dependency-arrays-the-engine-of-memoisation","Dependency arrays — the engine of memoisation",[15,217,218,219,222,223,226],{},"The second argument to both hooks is the ",[18,220,221],{},"dependency array",". After every\nrender, React compares each element to its previous value with ",[47,224,225],{},"Object.is",".\nIf any element changed, the cached result is thrown away and the factory\nfunction re-runs.",[15,228,229],{},"Three common mistakes:",[231,232,233,242,248],"ol",{},[44,234,235,241],{},[18,236,237,238],{},"Empty array ",[47,239,240],{},"[]"," — the factory runs once and is never invalidated.\nFine for truly static values; causes stale data bugs if any closed-over\nvalue can change.",[44,243,244,247],{},[18,245,246],{},"No array"," — the factory re-runs on every render, which defeats the\npurpose entirely.",[44,249,250,253,254,257],{},[18,251,252],{},"Object or array as a dependency"," — objects are compared by reference,\nso an inline ",[47,255,256],{},"{ limit: 10 }"," is a \"new\" dependency on every render even\nif its contents are identical.",[15,259,260,261,264,265,268],{},"The ",[47,262,263],{},"eslint-plugin-react-hooks"," package enforces the ",[18,266,267],{},"exhaustive-deps"," rule,\nwhich flags any reactive value used inside the factory but missing from the\narray. Always run this lint rule — it catches the majority of stale closure\nbugs automatically.",[10,270,272],{"id":271},"referential-stability-why-it-matters-downstream","Referential stability — why it matters downstream",[15,274,275,276,279,280,39],{},"JavaScript compares objects and functions by ",[18,277,278],{},"reference",", not by value.\nTwo objects with identical contents are not equal under ",[47,281,225],{},[76,283,287],{"className":284,"code":285,"language":286,"meta":81,"style":81},"language-js shiki shiki-themes github-light github-dark","Object.is({ a: 1 }, { a: 1 }) \u002F\u002F false — different references\nObject.is([1, 2], [1, 2])     \u002F\u002F false — different references\n","js",[47,288,289,314],{"__ignoreMap":81},[85,290,291,294,297,300,303,306,308,311],{"class":87,"line":88},[85,292,293],{"class":113},"Object.",[85,295,296],{"class":109},"is",[85,298,299],{"class":113},"({ a: ",[85,301,302],{"class":102},"1",[85,304,305],{"class":113}," }, { a: ",[85,307,302],{"class":102},[85,309,310],{"class":113}," }) ",[85,312,313],{"class":91},"\u002F\u002F false — different references\n",[85,315,316,318,320,323,325,328,331,334,336,338,340,343],{"class":87,"line":95},[85,317,293],{"class":113},[85,319,296],{"class":109},[85,321,322],{"class":113},"([",[85,324,302],{"class":102},[85,326,327],{"class":113},", ",[85,329,330],{"class":102},"2",[85,332,333],{"class":113},"], [",[85,335,302],{"class":102},[85,337,327],{"class":113},[85,339,330],{"class":102},[85,341,342],{"class":113},"])     ",[85,344,313],{"class":91},[15,346,347,348,327,351,354,355,357,358,360,361,363],{},"This matters because ",[47,349,350],{},"React.memo",[47,352,353],{},"useEffect",", and nested ",[47,356,20],{},"\u002F\n",[47,359,24],{}," hooks all use ",[47,362,225],{}," for their comparisons. If an object\nor function prop gets a new reference on every render, every downstream\noptimisation is silently broken.",[76,365,367],{"className":78,"code":366,"language":80,"meta":81,"style":81},"function Parent({ userId }) {\n  \u002F\u002F New reference every render — MemoizedChart always re-renders\n  const options = { userId, theme: 'dark' }\n\n  \u002F\u002F Stable reference — MemoizedChart only re-renders when userId changes\n  const options = useMemo(() => ({ userId, theme: 'dark' }), [userId])\n\n  return \u003CMemoizedChart options={options} \u002F>\n}\n",[47,368,369,387,392,412,416,421,443,447,466],{"__ignoreMap":81},[85,370,371,374,377,380,384],{"class":87,"line":88},[85,372,373],{"class":98},"function",[85,375,376],{"class":109}," Parent",[85,378,379],{"class":113},"({ ",[85,381,383],{"class":382},"s4XuR","userId",[85,385,386],{"class":113}," }) {\n",[85,388,389],{"class":87,"line":95},[85,390,391],{"class":91},"  \u002F\u002F New reference every render — MemoizedChart always re-renders\n",[85,393,394,397,400,402,405,409],{"class":87,"line":126},[85,395,396],{"class":98},"  const",[85,398,399],{"class":102}," options",[85,401,106],{"class":98},[85,403,404],{"class":113}," { userId, theme: ",[85,406,408],{"class":407},"sZZnC","'dark'",[85,410,411],{"class":113}," }\n",[85,413,414],{"class":87,"line":151},[85,415,155],{"emptyLinePlaceholder":154},[85,417,418],{"class":87,"line":158},[85,419,420],{"class":91},"  \u002F\u002F Stable reference — MemoizedChart only re-renders when userId changes\n",[85,422,423,425,427,429,431,433,435,438,440],{"class":87,"line":164},[85,424,396],{"class":98},[85,426,399],{"class":102},[85,428,106],{"class":98},[85,430,135],{"class":109},[85,432,114],{"class":113},[85,434,117],{"class":98},[85,436,437],{"class":113}," ({ userId, theme: ",[85,439,408],{"class":407},[85,441,442],{"class":113}," }), [userId])\n",[85,444,445],{"class":87,"line":179},[85,446,155],{"emptyLinePlaceholder":154},[85,448,449,452,455,458,460,463],{"class":87,"line":202},[85,450,451],{"class":98},"  return",[85,453,454],{"class":113}," \u003C",[85,456,457],{"class":102},"MemoizedChart",[85,459,399],{"class":109},[85,461,462],{"class":98},"=",[85,464,465],{"class":113},"{options} \u002F>\n",[85,467,468],{"class":87,"line":208},[85,469,470],{"class":113},"}\n",[15,472,473,474,476,477,479],{},"The rule is simple: before passing an object or function to a ",[47,475,350],{},"\ncomponent or into a ",[47,478,353],{}," dependency list, ensure its reference is\nstable.",[10,481,483],{"id":482},"when-not-to-memoize","When NOT to memoize",[15,485,486],{},"Memoisation has a real cost: React must store the previous value, run the\ndependency comparison after every render, and keep a closure alive in memory.\nFor cheap operations, this overhead can exceed the saving.",[15,488,489,490,21,492,494],{},"Skip ",[47,491,20],{},[47,493,24],{}," when:",[41,496,497,500,503,508],{},[44,498,499],{},"The computation is fast (arithmetic, string formatting, simple array ops\non small lists)",[44,501,502],{},"The dependency changes on every render anyway — memoisation never fires",[44,504,505,506],{},"The child receiving the callback is not wrapped in ",[47,507,350],{},[44,509,510],{},"You're in early development — premature optimisation obscures intent",[76,512,514],{"className":78,"code":513,"language":80,"meta":81,"style":81},"\u002F\u002F Unnecessary — wrapping trivial work\nconst label = useMemo(() => `Hello, ${name}`, [name])\nconst label = `Hello, ${name}` \u002F\u002F same cost, clearer code\n\n\u002F\u002F Unnecessary — the button doesn't care about reference stability\nconst handleClick = useCallback(() => setOpen(true), [])\nconst handleClick = () => setOpen(true) \u002F\u002F fine for DOM elements\n",[47,515,516,521,548,565,569,574,601],{"__ignoreMap":81},[85,517,518],{"class":87,"line":88},[85,519,520],{"class":91},"\u002F\u002F Unnecessary — wrapping trivial work\n",[85,522,523,525,528,530,532,534,536,539,542,545],{"class":87,"line":95},[85,524,99],{"class":98},[85,526,527],{"class":102}," label",[85,529,106],{"class":98},[85,531,135],{"class":109},[85,533,114],{"class":113},[85,535,117],{"class":98},[85,537,538],{"class":407}," `Hello, ${",[85,540,541],{"class":113},"name",[85,543,544],{"class":407},"}`",[85,546,547],{"class":113},", [name])\n",[85,549,550,552,554,556,558,560,562],{"class":87,"line":126},[85,551,99],{"class":98},[85,553,527],{"class":102},[85,555,106],{"class":98},[85,557,538],{"class":407},[85,559,541],{"class":113},[85,561,544],{"class":407},[85,563,564],{"class":91}," \u002F\u002F same cost, clearer code\n",[85,566,567],{"class":87,"line":151},[85,568,155],{"emptyLinePlaceholder":154},[85,570,571],{"class":87,"line":158},[85,572,573],{"class":91},"\u002F\u002F Unnecessary — the button doesn't care about reference stability\n",[85,575,576,578,581,583,585,587,589,592,595,598],{"class":87,"line":164},[85,577,99],{"class":98},[85,579,580],{"class":102}," handleClick",[85,582,106],{"class":98},[85,584,110],{"class":109},[85,586,114],{"class":113},[85,588,117],{"class":98},[85,590,591],{"class":109}," setOpen",[85,593,594],{"class":113},"(",[85,596,597],{"class":102},"true",[85,599,600],{"class":113},"), [])\n",[85,602,603,605,607,609,611,613,615,617,619,622],{"class":87,"line":179},[85,604,99],{"class":98},[85,606,580],{"class":109},[85,608,106],{"class":98},[85,610,142],{"class":113},[85,612,117],{"class":98},[85,614,591],{"class":109},[85,616,594],{"class":113},[85,618,597],{"class":102},[85,620,621],{"class":113},") ",[85,623,624],{"class":91},"\u002F\u002F fine for DOM elements\n",[15,626,627],{},"Profile with React DevTools Profiler before optimising. Add memoisation only\nwhere you can measure a render-time improvement.",[10,629,631],{"id":630},"memoizing-expensive-computations","Memoizing expensive computations",[15,633,634,635,39],{},"A computation is worth memoizing when it consistently takes more than ~1 ms\non mid-range hardware. You can check with ",[47,636,637],{},"console.time",[76,639,641],{"className":78,"code":640,"language":80,"meta":81,"style":81},"console.time('filter')\nconst result = largeDataset.filter(complexPredicate)\nconsole.timeEnd('filter') \u002F\u002F e.g. \"filter: 4.2 ms\" → worth memoizing\n\n\u002F\u002F Production pattern\nfunction ReportTable({ rawRows, activeFilter }) {\n  const rows = useMemo(\n    () => rawRows\n      .filter(r => matchesFilter(r, activeFilter))\n      .sort(bySeverity),\n    [rawRows, activeFilter]\n  )\n\n  return \u003CTable rows={rows} \u002F>\n}\n",[47,642,643,658,676,692,696,701,720,733,743,764,774,780,786,791,808],{"__ignoreMap":81},[85,644,645,648,651,653,656],{"class":87,"line":88},[85,646,647],{"class":113},"console.",[85,649,650],{"class":109},"time",[85,652,594],{"class":113},[85,654,655],{"class":407},"'filter'",[85,657,211],{"class":113},[85,659,660,662,665,667,670,673],{"class":87,"line":95},[85,661,99],{"class":98},[85,663,664],{"class":102}," result",[85,666,106],{"class":98},[85,668,669],{"class":113}," largeDataset.",[85,671,672],{"class":109},"filter",[85,674,675],{"class":113},"(complexPredicate)\n",[85,677,678,680,683,685,687,689],{"class":87,"line":126},[85,679,647],{"class":113},[85,681,682],{"class":109},"timeEnd",[85,684,594],{"class":113},[85,686,655],{"class":407},[85,688,621],{"class":113},[85,690,691],{"class":91},"\u002F\u002F e.g. \"filter: 4.2 ms\" → worth memoizing\n",[85,693,694],{"class":87,"line":151},[85,695,155],{"emptyLinePlaceholder":154},[85,697,698],{"class":87,"line":158},[85,699,700],{"class":91},"\u002F\u002F Production pattern\n",[85,702,703,705,708,710,713,715,718],{"class":87,"line":164},[85,704,373],{"class":98},[85,706,707],{"class":109}," ReportTable",[85,709,379],{"class":113},[85,711,712],{"class":382},"rawRows",[85,714,327],{"class":113},[85,716,717],{"class":382},"activeFilter",[85,719,386],{"class":113},[85,721,722,724,727,729,731],{"class":87,"line":179},[85,723,396],{"class":98},[85,725,726],{"class":102}," rows",[85,728,106],{"class":98},[85,730,135],{"class":109},[85,732,176],{"class":113},[85,734,735,738,740],{"class":87,"line":202},[85,736,737],{"class":113},"    () ",[85,739,117],{"class":98},[85,741,742],{"class":113}," rawRows\n",[85,744,745,748,750,752,755,758,761],{"class":87,"line":208},[85,746,747],{"class":113},"      .",[85,749,672],{"class":109},[85,751,594],{"class":113},[85,753,754],{"class":382},"r",[85,756,757],{"class":98}," =>",[85,759,760],{"class":109}," matchesFilter",[85,762,763],{"class":113},"(r, activeFilter))\n",[85,765,767,769,771],{"class":87,"line":766},10,[85,768,747],{"class":113},[85,770,196],{"class":109},[85,772,773],{"class":113},"(bySeverity),\n",[85,775,777],{"class":87,"line":776},11,[85,778,779],{"class":113},"    [rawRows, activeFilter]\n",[85,781,783],{"class":87,"line":782},12,[85,784,785],{"class":113},"  )\n",[85,787,789],{"class":87,"line":788},13,[85,790,155],{"emptyLinePlaceholder":154},[85,792,794,796,798,801,803,805],{"class":87,"line":793},14,[85,795,451],{"class":98},[85,797,454],{"class":113},[85,799,800],{"class":102},"Table",[85,802,726],{"class":109},[85,804,462],{"class":98},[85,806,807],{"class":113},"{rows} \u002F>\n",[85,809,811],{"class":87,"line":810},15,[85,812,470],{"class":113},[15,814,815,816,819],{},"Common genuinely expensive operations: sorting\u002Ffiltering large arrays\n(10 000+ items), heavy string processing, recursive tree traversals. Common\ncheap operations that don't need memoising: ",[47,817,818],{},".map"," over small arrays, simple\nstring concatenation, arithmetic.",[10,821,823],{"id":822},"combining-usememo-and-usecallback-with-reactmemo","Combining useMemo and useCallback with React.memo",[15,825,826,327,828,830,831,833,834,836],{},[47,827,350],{},[47,829,20],{},", and ",[47,832,24],{}," form a three-part system.\n",[47,835,350],{}," is the gate; the other two keep the keys from changing\nunnecessarily.",[76,838,840],{"className":78,"code":839,"language":80,"meta":81,"style":81},"const DataGrid = React.memo(function DataGrid({ rows, onRowClick }) {\n  console.log('DataGrid render')\n  return rows.map(row => (\n    \u003CRow key={row.id} data={row} onClick={onRowClick} \u002F>\n  ))\n})\n\nfunction Dashboard({ rawData, userId }) {\n  \u002F\u002F Stable computed rows — only changes when rawData changes\n  const rows = useMemo(\n    () => rawData.map(transformRow),\n    [rawData]\n  )\n\n  \u002F\u002F Stable handler — only changes when userId changes\n  const handleRowClick = useCallback(\n    (rowId) => navigateToDetail(userId, rowId),\n    [userId]\n  )\n\n  return \u003CDataGrid rows={rows} onRowClick={handleRowClick} \u002F>\n}\n",[47,841,842,875,890,910,942,947,952,956,974,979,991,1005,1010,1014,1018,1023,1037,1056,1062,1067,1072,1096],{"__ignoreMap":81},[85,843,844,846,849,851,854,857,859,861,863,865,868,870,873],{"class":87,"line":88},[85,845,99],{"class":98},[85,847,848],{"class":102}," DataGrid",[85,850,106],{"class":98},[85,852,853],{"class":113}," React.",[85,855,856],{"class":109},"memo",[85,858,594],{"class":113},[85,860,373],{"class":98},[85,862,848],{"class":109},[85,864,379],{"class":113},[85,866,867],{"class":382},"rows",[85,869,327],{"class":113},[85,871,872],{"class":382},"onRowClick",[85,874,386],{"class":113},[85,876,877,880,883,885,888],{"class":87,"line":95},[85,878,879],{"class":113},"  console.",[85,881,882],{"class":109},"log",[85,884,594],{"class":113},[85,886,887],{"class":407},"'DataGrid render'",[85,889,211],{"class":113},[85,891,892,894,897,900,902,905,907],{"class":87,"line":126},[85,893,451],{"class":98},[85,895,896],{"class":113}," rows.",[85,898,899],{"class":109},"map",[85,901,594],{"class":113},[85,903,904],{"class":382},"row",[85,906,757],{"class":98},[85,908,909],{"class":113}," (\n",[85,911,912,915,918,921,923,926,929,931,934,937,939],{"class":87,"line":151},[85,913,914],{"class":113},"    \u003C",[85,916,917],{"class":102},"Row",[85,919,920],{"class":109}," key",[85,922,462],{"class":98},[85,924,925],{"class":113},"{row.id} ",[85,927,928],{"class":109},"data",[85,930,462],{"class":98},[85,932,933],{"class":113},"{row} ",[85,935,936],{"class":109},"onClick",[85,938,462],{"class":98},[85,940,941],{"class":113},"{onRowClick} \u002F>\n",[85,943,944],{"class":87,"line":158},[85,945,946],{"class":113},"  ))\n",[85,948,949],{"class":87,"line":164},[85,950,951],{"class":113},"})\n",[85,953,954],{"class":87,"line":179},[85,955,155],{"emptyLinePlaceholder":154},[85,957,958,960,963,965,968,970,972],{"class":87,"line":202},[85,959,373],{"class":98},[85,961,962],{"class":109}," Dashboard",[85,964,379],{"class":113},[85,966,967],{"class":382},"rawData",[85,969,327],{"class":113},[85,971,383],{"class":382},[85,973,386],{"class":113},[85,975,976],{"class":87,"line":208},[85,977,978],{"class":91},"  \u002F\u002F Stable computed rows — only changes when rawData changes\n",[85,980,981,983,985,987,989],{"class":87,"line":766},[85,982,396],{"class":98},[85,984,726],{"class":102},[85,986,106],{"class":98},[85,988,135],{"class":109},[85,990,176],{"class":113},[85,992,993,995,997,1000,1002],{"class":87,"line":776},[85,994,737],{"class":113},[85,996,117],{"class":98},[85,998,999],{"class":113}," rawData.",[85,1001,899],{"class":109},[85,1003,1004],{"class":113},"(transformRow),\n",[85,1006,1007],{"class":87,"line":782},[85,1008,1009],{"class":113},"    [rawData]\n",[85,1011,1012],{"class":87,"line":788},[85,1013,785],{"class":113},[85,1015,1016],{"class":87,"line":793},[85,1017,155],{"emptyLinePlaceholder":154},[85,1019,1020],{"class":87,"line":810},[85,1021,1022],{"class":91},"  \u002F\u002F Stable handler — only changes when userId changes\n",[85,1024,1026,1028,1031,1033,1035],{"class":87,"line":1025},16,[85,1027,396],{"class":98},[85,1029,1030],{"class":102}," handleRowClick",[85,1032,106],{"class":98},[85,1034,110],{"class":109},[85,1036,176],{"class":113},[85,1038,1040,1043,1046,1048,1050,1053],{"class":87,"line":1039},17,[85,1041,1042],{"class":113},"    (",[85,1044,1045],{"class":382},"rowId",[85,1047,621],{"class":113},[85,1049,117],{"class":98},[85,1051,1052],{"class":109}," navigateToDetail",[85,1054,1055],{"class":113},"(userId, rowId),\n",[85,1057,1059],{"class":87,"line":1058},18,[85,1060,1061],{"class":113},"    [userId]\n",[85,1063,1065],{"class":87,"line":1064},19,[85,1066,785],{"class":113},[85,1068,1070],{"class":87,"line":1069},20,[85,1071,155],{"emptyLinePlaceholder":154},[85,1073,1075,1077,1079,1082,1084,1086,1089,1091,1093],{"class":87,"line":1074},21,[85,1076,451],{"class":98},[85,1078,454],{"class":113},[85,1080,1081],{"class":102},"DataGrid",[85,1083,726],{"class":109},[85,1085,462],{"class":98},[85,1087,1088],{"class":113},"{rows} ",[85,1090,872],{"class":109},[85,1092,462],{"class":98},[85,1094,1095],{"class":113},"{handleRowClick} \u002F>\n",[85,1097,1099],{"class":87,"line":1098},22,[85,1100,470],{"class":113},[15,1102,1103,1104,21,1106,1108,1109,1111,1112,1114,1115,21,1117,1119,1120,1123],{},"Without ",[47,1105,20],{},[47,1107,24],{}," above, ",[47,1110,1081],{},"'s ",[47,1113,350],{}," wrapper\nwould be a no-op — it would see \"new\" ",[47,1116,867],{},[47,1118,872],{}," on every\n",[47,1121,1122],{},"Dashboard"," render regardless of whether the actual data changed.",[10,1125,1127],{"id":1126},"stale-closures-the-hidden-correctness-bug","Stale closures — the hidden correctness bug",[15,1129,1130,1131,1134],{},"A ",[18,1132,1133],{},"stale closure"," occurs when a memoized function closes over a value that\nhas since changed because that value was omitted from the dependency array.",[76,1136,1138],{"className":78,"code":1137,"language":80,"meta":81,"style":81},"function Timer() {\n  const [count, setCount] = useState(0)\n\n  \u002F\u002F BUG: count is captured at 0 and never updated\n  const logCount = useCallback(() => {\n    console.log(count) \u002F\u002F always prints 0\n  }, []) \u002F\u002F ← missing `count`\n\n  \u002F\u002F FIX: add count to deps\n  const logCount = useCallback(() => {\n    console.log(count)\n  }, [count])\n\n  \u002F\u002F ALTERNATIVE FIX for state setters: use functional update\n  const increment = useCallback(() => {\n    setCount(prev => prev + 1) \u002F\u002F reads latest state, no dep needed\n  }, [])\n}\n",[47,1139,1140,1150,1179,1183,1188,1206,1219,1227,1231,1236,1252,1261,1266,1270,1275,1292,1318,1323],{"__ignoreMap":81},[85,1141,1142,1144,1147],{"class":87,"line":88},[85,1143,373],{"class":98},[85,1145,1146],{"class":109}," Timer",[85,1148,1149],{"class":113},"() {\n",[85,1151,1152,1154,1156,1159,1161,1164,1167,1169,1172,1174,1177],{"class":87,"line":95},[85,1153,396],{"class":98},[85,1155,187],{"class":113},[85,1157,1158],{"class":102},"count",[85,1160,327],{"class":113},[85,1162,1163],{"class":102},"setCount",[85,1165,1166],{"class":113},"] ",[85,1168,462],{"class":98},[85,1170,1171],{"class":109}," useState",[85,1173,594],{"class":113},[85,1175,1176],{"class":102},"0",[85,1178,211],{"class":113},[85,1180,1181],{"class":87,"line":126},[85,1182,155],{"emptyLinePlaceholder":154},[85,1184,1185],{"class":87,"line":151},[85,1186,1187],{"class":91},"  \u002F\u002F BUG: count is captured at 0 and never updated\n",[85,1189,1190,1192,1195,1197,1199,1201,1203],{"class":87,"line":158},[85,1191,396],{"class":98},[85,1193,1194],{"class":102}," logCount",[85,1196,106],{"class":98},[85,1198,110],{"class":109},[85,1200,114],{"class":113},[85,1202,117],{"class":98},[85,1204,1205],{"class":113}," {\n",[85,1207,1208,1211,1213,1216],{"class":87,"line":164},[85,1209,1210],{"class":113},"    console.",[85,1212,882],{"class":109},[85,1214,1215],{"class":113},"(count) ",[85,1217,1218],{"class":91},"\u002F\u002F always prints 0\n",[85,1220,1221,1224],{"class":87,"line":179},[85,1222,1223],{"class":113},"  }, []) ",[85,1225,1226],{"class":91},"\u002F\u002F ← missing `count`\n",[85,1228,1229],{"class":87,"line":202},[85,1230,155],{"emptyLinePlaceholder":154},[85,1232,1233],{"class":87,"line":208},[85,1234,1235],{"class":91},"  \u002F\u002F FIX: add count to deps\n",[85,1237,1238,1240,1242,1244,1246,1248,1250],{"class":87,"line":766},[85,1239,396],{"class":98},[85,1241,1194],{"class":102},[85,1243,106],{"class":98},[85,1245,110],{"class":109},[85,1247,114],{"class":113},[85,1249,117],{"class":98},[85,1251,1205],{"class":113},[85,1253,1254,1256,1258],{"class":87,"line":776},[85,1255,1210],{"class":113},[85,1257,882],{"class":109},[85,1259,1260],{"class":113},"(count)\n",[85,1262,1263],{"class":87,"line":782},[85,1264,1265],{"class":113},"  }, [count])\n",[85,1267,1268],{"class":87,"line":788},[85,1269,155],{"emptyLinePlaceholder":154},[85,1271,1272],{"class":87,"line":793},[85,1273,1274],{"class":91},"  \u002F\u002F ALTERNATIVE FIX for state setters: use functional update\n",[85,1276,1277,1279,1282,1284,1286,1288,1290],{"class":87,"line":810},[85,1278,396],{"class":98},[85,1280,1281],{"class":102}," increment",[85,1283,106],{"class":98},[85,1285,110],{"class":109},[85,1287,114],{"class":113},[85,1289,117],{"class":98},[85,1291,1205],{"class":113},[85,1293,1294,1297,1299,1302,1304,1307,1310,1313,1315],{"class":87,"line":1025},[85,1295,1296],{"class":109},"    setCount",[85,1298,594],{"class":113},[85,1300,1301],{"class":382},"prev",[85,1303,757],{"class":98},[85,1305,1306],{"class":113}," prev ",[85,1308,1309],{"class":98},"+",[85,1311,1312],{"class":102}," 1",[85,1314,621],{"class":113},[85,1316,1317],{"class":91},"\u002F\u002F reads latest state, no dep needed\n",[85,1319,1320],{"class":87,"line":1039},[85,1321,1322],{"class":113},"  }, [])\n",[85,1324,1325],{"class":87,"line":1058},[85,1326,470],{"class":113},[15,1328,260,1329,1331,1332,1334],{},[47,1330,263],{}," ",[47,1333,267],{}," rule catches most stale\nclosures. Never omit a dependency to preserve a stable reference — instead,\nuse functional state updates or the ref-callback pattern to legitimately\nremove the dependency.",[10,1336,1338],{"id":1337},"practical-patterns","Practical patterns",[15,1340,1341,1344,1345,1347],{},[18,1342,1343],{},"Context provider memoisation"," — wrap the context value in ",[47,1346,20],{}," to\nprevent all consumers re-rendering on every provider render:",[76,1349,1351],{"className":78,"code":1350,"language":80,"meta":81,"style":81},"function AuthProvider({ children }) {\n  const [user, setUser] = useState(null)\n  const value = useMemo(() => ({ user, setUser }), [user])\n  return \u003CAuthContext.Provider value={value}>{children}\u003C\u002FAuthContext.Provider>\n}\n",[47,1352,1353,1367,1394,1412,1433],{"__ignoreMap":81},[85,1354,1355,1357,1360,1362,1365],{"class":87,"line":88},[85,1356,373],{"class":98},[85,1358,1359],{"class":109}," AuthProvider",[85,1361,379],{"class":113},[85,1363,1364],{"class":382},"children",[85,1366,386],{"class":113},[85,1368,1369,1371,1373,1376,1378,1381,1383,1385,1387,1389,1392],{"class":87,"line":95},[85,1370,396],{"class":98},[85,1372,187],{"class":113},[85,1374,1375],{"class":102},"user",[85,1377,327],{"class":113},[85,1379,1380],{"class":102},"setUser",[85,1382,1166],{"class":113},[85,1384,462],{"class":98},[85,1386,1171],{"class":109},[85,1388,594],{"class":113},[85,1390,1391],{"class":102},"null",[85,1393,211],{"class":113},[85,1395,1396,1398,1401,1403,1405,1407,1409],{"class":87,"line":126},[85,1397,396],{"class":98},[85,1399,1400],{"class":102}," value",[85,1402,106],{"class":98},[85,1404,135],{"class":109},[85,1406,114],{"class":113},[85,1408,117],{"class":98},[85,1410,1411],{"class":113}," ({ user, setUser }), [user])\n",[85,1413,1414,1416,1418,1421,1423,1425,1428,1430],{"class":87,"line":151},[85,1415,451],{"class":98},[85,1417,454],{"class":113},[85,1419,1420],{"class":102},"AuthContext.Provider",[85,1422,1400],{"class":109},[85,1424,462],{"class":98},[85,1426,1427],{"class":113},"{value}>{children}\u003C\u002F",[85,1429,1420],{"class":102},[85,1431,1432],{"class":113},">\n",[85,1434,1435],{"class":87,"line":158},[85,1436,470],{"class":113},[15,1438,1439,1442,1443,1445,1446,1448],{},[18,1440,1441],{},"Custom hooks"," — always wrap returned functions in ",[47,1444,24],{}," so\nconsumers can safely list them as ",[47,1447,353],{}," dependencies:",[76,1450,1452],{"className":78,"code":1451,"language":80,"meta":81,"style":81},"function useSearch(query) {\n  const search = useCallback(\n    () => fetchResults(query),\n    [query]\n  )\n  return { search }\n}\n",[47,1453,1454,1469,1482,1494,1499,1503,1510],{"__ignoreMap":81},[85,1455,1456,1458,1461,1463,1466],{"class":87,"line":88},[85,1457,373],{"class":98},[85,1459,1460],{"class":109}," useSearch",[85,1462,594],{"class":113},[85,1464,1465],{"class":382},"query",[85,1467,1468],{"class":113},") {\n",[85,1470,1471,1473,1476,1478,1480],{"class":87,"line":95},[85,1472,396],{"class":98},[85,1474,1475],{"class":102}," search",[85,1477,106],{"class":98},[85,1479,110],{"class":109},[85,1481,176],{"class":113},[85,1483,1484,1486,1488,1491],{"class":87,"line":126},[85,1485,737],{"class":113},[85,1487,117],{"class":98},[85,1489,1490],{"class":109}," fetchResults",[85,1492,1493],{"class":113},"(query),\n",[85,1495,1496],{"class":87,"line":151},[85,1497,1498],{"class":113},"    [query]\n",[85,1500,1501],{"class":87,"line":158},[85,1502,785],{"class":113},[85,1504,1505,1507],{"class":87,"line":164},[85,1506,451],{"class":98},[85,1508,1509],{"class":113}," { search }\n",[85,1511,1512],{"class":87,"line":179},[85,1513,470],{"class":113},[15,1515,1516,1519],{},[18,1517,1518],{},"Object dependencies"," — depend on primitives, not objects, to avoid\nmemoisation that never fires:",[76,1521,1523],{"className":78,"code":1522,"language":80,"meta":81,"style":81},"\u002F\u002F Bad — options is a new reference every render\nconst result = useMemo(() => compute(options), [options])\n\n\u002F\u002F Good — depend on the primitives inside\nconst result = useMemo(() => compute({ limit, offset }), [limit, offset])\n",[47,1524,1525,1530,1550,1554,1559],{"__ignoreMap":81},[85,1526,1527],{"class":87,"line":88},[85,1528,1529],{"class":91},"\u002F\u002F Bad — options is a new reference every render\n",[85,1531,1532,1534,1536,1538,1540,1542,1544,1547],{"class":87,"line":95},[85,1533,99],{"class":98},[85,1535,664],{"class":102},[85,1537,106],{"class":98},[85,1539,135],{"class":109},[85,1541,114],{"class":113},[85,1543,117],{"class":98},[85,1545,1546],{"class":109}," compute",[85,1548,1549],{"class":113},"(options), [options])\n",[85,1551,1552],{"class":87,"line":126},[85,1553,155],{"emptyLinePlaceholder":154},[85,1555,1556],{"class":87,"line":151},[85,1557,1558],{"class":91},"\u002F\u002F Good — depend on the primitives inside\n",[85,1560,1561,1563,1565,1567,1569,1571,1573,1575],{"class":87,"line":158},[85,1562,99],{"class":98},[85,1564,664],{"class":102},[85,1566,106],{"class":98},[85,1568,135],{"class":109},[85,1570,114],{"class":113},[85,1572,117],{"class":98},[85,1574,1546],{"class":109},[85,1576,1577],{"class":113},"({ limit, offset }), [limit, offset])\n",[15,1579,1580],{},"Memoisation is a scalpel, not a bandage. Apply it precisely where profiling\nshows a real cost, and the rest of your component tree stays simple and fast.",[1582,1583,1584],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":81,"searchDepth":95,"depth":95,"links":1586},[1587,1588,1589,1590,1591,1592,1593,1594,1595],{"id":12,"depth":95,"text":13},{"id":31,"depth":95,"text":32},{"id":214,"depth":95,"text":215},{"id":271,"depth":95,"text":272},{"id":482,"depth":95,"text":483},{"id":630,"depth":95,"text":631},{"id":822,"depth":95,"text":823},{"id":1126,"depth":95,"text":1127},{"id":1337,"depth":95,"text":1338},"React useMemo and useCallback interview questions — memoization patterns, dependency arrays, when to use each hook, referential stability, and common pitfalls.","medium","md","React","react",{},"\u002Fblog\u002Freact-usememo-usecallback-patterns-guide","\u002Freact\u002Frendering-and-performance\u002Fusememo-usecallback-patterns",{"title":5,"description":1596},"blog\u002Freact-usememo-usecallback-patterns-guide","useMemo and useCallback Patterns","Rendering and Performance","rendering-and-performance","2026-06-24","oNPEgWXSYu7Y_-WLoPTARsPtEPg8fqg7iJDdzdL03pQ",1782244083221]