[{"data":1,"prerenderedAt":2588},["ShallowReactive",2],{"page-\u002Freact\u002F06-useeffect-and-lifecycle":3},{"id":4,"title":5,"body":6,"description":2581,"extension":2582,"meta":2583,"navigation":62,"path":2584,"seo":2585,"stem":2586,"__hash__":2587},"content\u002Freact\u002F06-useeffect-and-lifecycle.md","06 — useEffect & Lifecycle",{"type":7,"value":8,"toc":2569},"minimark",[9,19,24,329,333,530,534,1048,1052,1274,1278,1632,1636,1922,1926,2020,2024,2142,2146,2149,2301,2473,2477,2565],[10,11,13,14,18],"h1",{"id":12},"_06-useeffect-lifecycle","06 — ",[15,16,17],"code",{},"useEffect"," & Lifecycle",[20,21,23],"h2",{"id":22},"the-effect-lifecycle","The Effect Lifecycle",[25,26,29],"code-wrapper",{"filename":27,"language":28},"effect_lifecycle.js","javascript",[30,31,35],"pre",{"className":32,"code":33,"language":28,"meta":34,"style":34},"language-javascript shiki shiki-themes github-light github-dark","import { useEffect } from 'react'\n\n\u002F\u002F useEffect runs AFTER the browser paints (asynchronous — not during render).\n\u002F\u002F The dependency array controls WHICH phases of the lifecycle trigger the effect:\n\n\u002F\u002F ┌─────────────────────────────────────────────────────────────────────┐\n\u002F\u002F │  deps array   │  when the effect runs                               │\n\u002F\u002F ├───────────────┼─────────────────────────────────────────────────────┤\n\u002F\u002F │  [a, b]       │  on mount + when a or b changes (Object.is compare)  │\n\u002F\u002F │  []           │  on mount ONLY (never on update)                     │\n\u002F\u002F │  (omitted)    │  after EVERY render (mount + every update)           │\n\u002F\u002F └─────────────────────────────────────────────────────────────────────┘\n\nfunction DataViewer({ resourceId }) {\n  const [data, setData] = useState(null)\n\n  useEffect(() => {\n    \u002F\u002F Effect body runs AFTER paint\n    fetchData(resourceId).then(setData)\n\n    \u002F\u002F Optional cleanup: runs BEFORE the next effect AND on unmount\n    return () => {\n      console.log('cleanup: resourceId changed or component unmounting')\n    }\n  }, [resourceId])  \u002F\u002F runs on mount + when resourceId changes\n\n  \u002F\u002F LIFECYCLE SEQUENCE for resourceId changing from 1 → 2:\n  \u002F\u002F 1. Render with resourceId=2 (new state\u002Fprops trigger re-render)\n  \u002F\u002F 2. Browser paints with new resourceId\n  \u002F\u002F 3. Cleanup from PREVIOUS effect runs (resourceId was 1)\n  \u002F\u002F 4. NEW effect body runs (resourceId is now 2)\n  \u002F\u002F → cleanup → effect → cleanup → effect → ... → final cleanup on unmount\n}\n","",[15,36,37,57,64,71,77,82,88,94,100,106,112,118,124,129,149,186,191,206,212,227,232,238,251,267,273,282,287,293,299,305,311,317,323],{"__ignoreMap":34},[38,39,42,46,50,53],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"svdQ7","import",[38,47,49],{"class":48},"ssxIu"," { useEffect } ",[38,51,52],{"class":44},"from",[38,54,56],{"class":55},"sJ6F3"," 'react'\n",[38,58,60],{"class":40,"line":59},2,[38,61,63],{"emptyLinePlaceholder":62},true,"\n",[38,65,67],{"class":40,"line":66},3,[38,68,70],{"class":69},"sdCPZ","\u002F\u002F useEffect runs AFTER the browser paints (asynchronous — not during render).\n",[38,72,74],{"class":40,"line":73},4,[38,75,76],{"class":69},"\u002F\u002F The dependency array controls WHICH phases of the lifecycle trigger the effect:\n",[38,78,80],{"class":40,"line":79},5,[38,81,63],{"emptyLinePlaceholder":62},[38,83,85],{"class":40,"line":84},6,[38,86,87],{"class":69},"\u002F\u002F ┌─────────────────────────────────────────────────────────────────────┐\n",[38,89,91],{"class":40,"line":90},7,[38,92,93],{"class":69},"\u002F\u002F │  deps array   │  when the effect runs                               │\n",[38,95,97],{"class":40,"line":96},8,[38,98,99],{"class":69},"\u002F\u002F ├───────────────┼─────────────────────────────────────────────────────┤\n",[38,101,103],{"class":40,"line":102},9,[38,104,105],{"class":69},"\u002F\u002F │  [a, b]       │  on mount + when a or b changes (Object.is compare)  │\n",[38,107,109],{"class":40,"line":108},10,[38,110,111],{"class":69},"\u002F\u002F │  []           │  on mount ONLY (never on update)                     │\n",[38,113,115],{"class":40,"line":114},11,[38,116,117],{"class":69},"\u002F\u002F │  (omitted)    │  after EVERY render (mount + every update)           │\n",[38,119,121],{"class":40,"line":120},12,[38,122,123],{"class":69},"\u002F\u002F └─────────────────────────────────────────────────────────────────────┘\n",[38,125,127],{"class":40,"line":126},13,[38,128,63],{"emptyLinePlaceholder":62},[38,130,132,135,139,142,146],{"class":40,"line":131},14,[38,133,134],{"class":44},"function",[38,136,138],{"class":137},"sIsaT"," DataViewer",[38,140,141],{"class":48},"({ ",[38,143,145],{"class":144},"sCrzJ","resourceId",[38,147,148],{"class":48}," }) {\n",[38,150,152,155,158,162,165,168,171,174,177,180,183],{"class":40,"line":151},15,[38,153,154],{"class":44},"  const",[38,156,157],{"class":48}," [",[38,159,161],{"class":160},"snvgF","data",[38,163,164],{"class":48},", ",[38,166,167],{"class":160},"setData",[38,169,170],{"class":48},"] ",[38,172,173],{"class":44},"=",[38,175,176],{"class":137}," useState",[38,178,179],{"class":48},"(",[38,181,182],{"class":160},"null",[38,184,185],{"class":48},")\n",[38,187,189],{"class":40,"line":188},16,[38,190,63],{"emptyLinePlaceholder":62},[38,192,194,197,200,203],{"class":40,"line":193},17,[38,195,196],{"class":137},"  useEffect",[38,198,199],{"class":48},"(() ",[38,201,202],{"class":44},"=>",[38,204,205],{"class":48}," {\n",[38,207,209],{"class":40,"line":208},18,[38,210,211],{"class":69},"    \u002F\u002F Effect body runs AFTER paint\n",[38,213,215,218,221,224],{"class":40,"line":214},19,[38,216,217],{"class":137},"    fetchData",[38,219,220],{"class":48},"(resourceId).",[38,222,223],{"class":137},"then",[38,225,226],{"class":48},"(setData)\n",[38,228,230],{"class":40,"line":229},20,[38,231,63],{"emptyLinePlaceholder":62},[38,233,235],{"class":40,"line":234},21,[38,236,237],{"class":69},"    \u002F\u002F Optional cleanup: runs BEFORE the next effect AND on unmount\n",[38,239,241,244,247,249],{"class":40,"line":240},22,[38,242,243],{"class":44},"    return",[38,245,246],{"class":48}," () ",[38,248,202],{"class":44},[38,250,205],{"class":48},[38,252,254,257,260,262,265],{"class":40,"line":253},23,[38,255,256],{"class":48},"      console.",[38,258,259],{"class":137},"log",[38,261,179],{"class":48},[38,263,264],{"class":55},"'cleanup: resourceId changed or component unmounting'",[38,266,185],{"class":48},[38,268,270],{"class":40,"line":269},24,[38,271,272],{"class":48},"    }\n",[38,274,276,279],{"class":40,"line":275},25,[38,277,278],{"class":48},"  }, [resourceId])  ",[38,280,281],{"class":69},"\u002F\u002F runs on mount + when resourceId changes\n",[38,283,285],{"class":40,"line":284},26,[38,286,63],{"emptyLinePlaceholder":62},[38,288,290],{"class":40,"line":289},27,[38,291,292],{"class":69},"  \u002F\u002F LIFECYCLE SEQUENCE for resourceId changing from 1 → 2:\n",[38,294,296],{"class":40,"line":295},28,[38,297,298],{"class":69},"  \u002F\u002F 1. Render with resourceId=2 (new state\u002Fprops trigger re-render)\n",[38,300,302],{"class":40,"line":301},29,[38,303,304],{"class":69},"  \u002F\u002F 2. Browser paints with new resourceId\n",[38,306,308],{"class":40,"line":307},30,[38,309,310],{"class":69},"  \u002F\u002F 3. Cleanup from PREVIOUS effect runs (resourceId was 1)\n",[38,312,314],{"class":40,"line":313},31,[38,315,316],{"class":69},"  \u002F\u002F 4. NEW effect body runs (resourceId is now 2)\n",[38,318,320],{"class":40,"line":319},32,[38,321,322],{"class":69},"  \u002F\u002F → cleanup → effect → cleanup → effect → ... → final cleanup on unmount\n",[38,324,326],{"class":40,"line":325},33,[38,327,328],{"class":48},"}\n",[20,330,332],{"id":331},"dependency-array-objectis-comparison","Dependency Array: Object.is Comparison",[25,334,336],{"filename":335,"language":28},"deps_comparison.js",[30,337,339],{"className":32,"code":338,"language":28,"meta":34,"style":34},"\u002F\u002F React compares each dep with Object.is(prevDep, nextDep).\n\u002F\u002F If ANY dep fails Object.is, the effect re-runs.\n\n\u002F\u002F Object.is works for primitives (strings, numbers, booleans):\nuseEffect(() => { \u002F* runs when id changes *\u002F }, [id])  \u002F\u002F ✓ id=1 vs id=2 → re-runs\n\n\u002F\u002F Object.is FAILS for new object\u002Farray\u002Ffunction references:\nuseEffect(() => { \u002F* runs every render! *\u002F }, [{ page: 1 }])\n\u002F\u002F { page: 1 } !== { page: 1 } — new object literal every render → always different\n\nuseEffect(() => { \u002F* runs every render! *\u002F }, [() => doSomething()])\n\u002F\u002F New function reference every render → always different\n\n\u002F\u002F THIS IS WHY useMemo\u002FuseCallback EXIST:\nconst config = useMemo(() => ({ page: 1 }), [])  \u002F\u002F stable reference\nconst handler = useCallback(() => doSomething(), [doSomething])  \u002F\u002F stable reference\nuseEffect(() => { \u002F* runs only when config or handler reference changes *\u002F }, [config, handler])\n\u002F\u002F Now the effect only re-runs when the memoized values actually change.\n",[15,340,341,346,351,355,360,380,384,389,411,416,420,443,448,452,457,486,509,525],{"__ignoreMap":34},[38,342,343],{"class":40,"line":41},[38,344,345],{"class":69},"\u002F\u002F React compares each dep with Object.is(prevDep, nextDep).\n",[38,347,348],{"class":40,"line":59},[38,349,350],{"class":69},"\u002F\u002F If ANY dep fails Object.is, the effect re-runs.\n",[38,352,353],{"class":40,"line":66},[38,354,63],{"emptyLinePlaceholder":62},[38,356,357],{"class":40,"line":73},[38,358,359],{"class":69},"\u002F\u002F Object.is works for primitives (strings, numbers, booleans):\n",[38,361,362,364,366,368,371,374,377],{"class":40,"line":79},[38,363,17],{"class":137},[38,365,199],{"class":48},[38,367,202],{"class":44},[38,369,370],{"class":48}," { ",[38,372,373],{"class":69},"\u002F* runs when id changes *\u002F",[38,375,376],{"class":48}," }, [id])  ",[38,378,379],{"class":69},"\u002F\u002F ✓ id=1 vs id=2 → re-runs\n",[38,381,382],{"class":40,"line":84},[38,383,63],{"emptyLinePlaceholder":62},[38,385,386],{"class":40,"line":90},[38,387,388],{"class":69},"\u002F\u002F Object.is FAILS for new object\u002Farray\u002Ffunction references:\n",[38,390,391,393,395,397,399,402,405,408],{"class":40,"line":96},[38,392,17],{"class":137},[38,394,199],{"class":48},[38,396,202],{"class":44},[38,398,370],{"class":48},[38,400,401],{"class":69},"\u002F* runs every render! *\u002F",[38,403,404],{"class":48}," }, [{ page: ",[38,406,407],{"class":160},"1",[38,409,410],{"class":48}," }])\n",[38,412,413],{"class":40,"line":102},[38,414,415],{"class":69},"\u002F\u002F { page: 1 } !== { page: 1 } — new object literal every render → always different\n",[38,417,418],{"class":40,"line":108},[38,419,63],{"emptyLinePlaceholder":62},[38,421,422,424,426,428,430,432,435,437,440],{"class":40,"line":114},[38,423,17],{"class":137},[38,425,199],{"class":48},[38,427,202],{"class":44},[38,429,370],{"class":48},[38,431,401],{"class":69},[38,433,434],{"class":48}," }, [() ",[38,436,202],{"class":44},[38,438,439],{"class":137}," doSomething",[38,441,442],{"class":48},"()])\n",[38,444,445],{"class":40,"line":120},[38,446,447],{"class":69},"\u002F\u002F New function reference every render → always different\n",[38,449,450],{"class":40,"line":126},[38,451,63],{"emptyLinePlaceholder":62},[38,453,454],{"class":40,"line":131},[38,455,456],{"class":69},"\u002F\u002F THIS IS WHY useMemo\u002FuseCallback EXIST:\n",[38,458,459,462,465,468,471,473,475,478,480,483],{"class":40,"line":151},[38,460,461],{"class":44},"const",[38,463,464],{"class":160}," config",[38,466,467],{"class":44}," =",[38,469,470],{"class":137}," useMemo",[38,472,199],{"class":48},[38,474,202],{"class":44},[38,476,477],{"class":48}," ({ page: ",[38,479,407],{"class":160},[38,481,482],{"class":48}," }), [])  ",[38,484,485],{"class":69},"\u002F\u002F stable reference\n",[38,487,488,490,493,495,498,500,502,504,507],{"class":40,"line":188},[38,489,461],{"class":44},[38,491,492],{"class":160}," handler",[38,494,467],{"class":44},[38,496,497],{"class":137}," useCallback",[38,499,199],{"class":48},[38,501,202],{"class":44},[38,503,439],{"class":137},[38,505,506],{"class":48},"(), [doSomething])  ",[38,508,485],{"class":69},[38,510,511,513,515,517,519,522],{"class":40,"line":193},[38,512,17],{"class":137},[38,514,199],{"class":48},[38,516,202],{"class":44},[38,518,370],{"class":48},[38,520,521],{"class":69},"\u002F* runs only when config or handler reference changes *\u002F",[38,523,524],{"class":48}," }, [config, handler])\n",[38,526,527],{"class":40,"line":208},[38,528,529],{"class":69},"\u002F\u002F Now the effect only re-runs when the memoized values actually change.\n",[20,531,533],{"id":532},"async-race-conditions","Async Race Conditions",[25,535,537],{"filename":536,"language":28},"race_condition.js",[30,538,540],{"className":32,"code":539,"language":28,"meta":34,"style":34},"\u002F\u002F ANTI-PATTERN: no cancellation — stale responses overwrite fresh data\nfunction BadProfile({ userId }) {\n  const [profile, setProfile] = useState(null)\n  useEffect(() => {\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n      .then(res => res.json())\n      .then(data => setProfile(data))\n  }, [userId])\n  \u002F\u002F BUG: user switches from userId=1 to userId=2 quickly.\n  \u002F\u002F Request 2 fires, then request 1 (slow) resolves LAST → overwrites\n  \u002F\u002F profile with userId=1's data while userId=2 is the active prop.\n  return \u003Cdiv>{profile?.name}\u003C\u002Fdiv>\n}\n\n\u002F\u002F PRODUCTION: AbortController for cancellation\nfunction GoodProfile({ userId }) {\n  const [profile, setProfile] = useState(null)\n  useEffect(() => {\n    const controller = new AbortController()\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`, { signal: controller.signal })\n      .then(res => res.json())\n      .then(data => setProfile(data))\n      .catch(err => {\n        if (err.name !== 'AbortError') throw err  \u002F\u002F re-throw real errors\n      })\n    return () => controller.abort()  \u002F\u002F cancels the in-flight request on cleanup\n  }, [userId])\n  \u002F\u002F When userId changes: cleanup aborts the old request → no stale overwrite.\n  return \u003Cdiv>{profile?.name}\u003C\u002Fdiv>\n}\n\n\u002F\u002F ALTERNATIVE: stale flag (if you can't use AbortController)\nuseEffect(() => {\n  let isStale = false\n  fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(res => res.json()).then(data => {\n    if (!isStale) setProfile(data)  \u002F\u002F only set if this effect is still current\n  })\n  return () => { isStale = true }\n}, [userId])\n",[15,541,542,547,561,587,597,614,638,656,661,666,671,676,696,700,704,709,722,746,756,775,790,808,824,840,866,871,891,895,900,914,918,922,927,937,951,993,1016,1022,1042],{"__ignoreMap":34},[38,543,544],{"class":40,"line":41},[38,545,546],{"class":69},"\u002F\u002F ANTI-PATTERN: no cancellation — stale responses overwrite fresh data\n",[38,548,549,551,554,556,559],{"class":40,"line":59},[38,550,134],{"class":44},[38,552,553],{"class":137}," BadProfile",[38,555,141],{"class":48},[38,557,558],{"class":144},"userId",[38,560,148],{"class":48},[38,562,563,565,567,570,572,575,577,579,581,583,585],{"class":40,"line":66},[38,564,154],{"class":44},[38,566,157],{"class":48},[38,568,569],{"class":160},"profile",[38,571,164],{"class":48},[38,573,574],{"class":160},"setProfile",[38,576,170],{"class":48},[38,578,173],{"class":44},[38,580,176],{"class":137},[38,582,179],{"class":48},[38,584,182],{"class":160},[38,586,185],{"class":48},[38,588,589,591,593,595],{"class":40,"line":73},[38,590,196],{"class":137},[38,592,199],{"class":48},[38,594,202],{"class":44},[38,596,205],{"class":48},[38,598,599,602,604,607,609,612],{"class":40,"line":79},[38,600,601],{"class":137},"    fetch",[38,603,179],{"class":48},[38,605,606],{"class":55},"`\u002Fapi\u002Fusers\u002F${",[38,608,558],{"class":48},[38,610,611],{"class":55},"}`",[38,613,185],{"class":48},[38,615,616,619,621,623,626,629,632,635],{"class":40,"line":84},[38,617,618],{"class":48},"      .",[38,620,223],{"class":137},[38,622,179],{"class":48},[38,624,625],{"class":144},"res",[38,627,628],{"class":44}," =>",[38,630,631],{"class":48}," res.",[38,633,634],{"class":137},"json",[38,636,637],{"class":48},"())\n",[38,639,640,642,644,646,648,650,653],{"class":40,"line":90},[38,641,618],{"class":48},[38,643,223],{"class":137},[38,645,179],{"class":48},[38,647,161],{"class":144},[38,649,628],{"class":44},[38,651,652],{"class":137}," setProfile",[38,654,655],{"class":48},"(data))\n",[38,657,658],{"class":40,"line":96},[38,659,660],{"class":48},"  }, [userId])\n",[38,662,663],{"class":40,"line":102},[38,664,665],{"class":69},"  \u002F\u002F BUG: user switches from userId=1 to userId=2 quickly.\n",[38,667,668],{"class":40,"line":108},[38,669,670],{"class":69},"  \u002F\u002F Request 2 fires, then request 1 (slow) resolves LAST → overwrites\n",[38,672,673],{"class":40,"line":114},[38,674,675],{"class":69},"  \u002F\u002F profile with userId=1's data while userId=2 is the active prop.\n",[38,677,678,681,684,688,691,693],{"class":40,"line":120},[38,679,680],{"class":44},"  return",[38,682,683],{"class":48}," \u003C",[38,685,687],{"class":686},"sk71V","div",[38,689,690],{"class":48},">{profile?.name}\u003C\u002F",[38,692,687],{"class":686},[38,694,695],{"class":48},">\n",[38,697,698],{"class":40,"line":126},[38,699,328],{"class":48},[38,701,702],{"class":40,"line":131},[38,703,63],{"emptyLinePlaceholder":62},[38,705,706],{"class":40,"line":151},[38,707,708],{"class":69},"\u002F\u002F PRODUCTION: AbortController for cancellation\n",[38,710,711,713,716,718,720],{"class":40,"line":188},[38,712,134],{"class":44},[38,714,715],{"class":137}," GoodProfile",[38,717,141],{"class":48},[38,719,558],{"class":144},[38,721,148],{"class":48},[38,723,724,726,728,730,732,734,736,738,740,742,744],{"class":40,"line":193},[38,725,154],{"class":44},[38,727,157],{"class":48},[38,729,569],{"class":160},[38,731,164],{"class":48},[38,733,574],{"class":160},[38,735,170],{"class":48},[38,737,173],{"class":44},[38,739,176],{"class":137},[38,741,179],{"class":48},[38,743,182],{"class":160},[38,745,185],{"class":48},[38,747,748,750,752,754],{"class":40,"line":208},[38,749,196],{"class":137},[38,751,199],{"class":48},[38,753,202],{"class":44},[38,755,205],{"class":48},[38,757,758,761,764,766,769,772],{"class":40,"line":214},[38,759,760],{"class":44},"    const",[38,762,763],{"class":160}," controller",[38,765,467],{"class":44},[38,767,768],{"class":44}," new",[38,770,771],{"class":137}," AbortController",[38,773,774],{"class":48},"()\n",[38,776,777,779,781,783,785,787],{"class":40,"line":229},[38,778,601],{"class":137},[38,780,179],{"class":48},[38,782,606],{"class":55},[38,784,558],{"class":48},[38,786,611],{"class":55},[38,788,789],{"class":48},", { signal: controller.signal })\n",[38,791,792,794,796,798,800,802,804,806],{"class":40,"line":234},[38,793,618],{"class":48},[38,795,223],{"class":137},[38,797,179],{"class":48},[38,799,625],{"class":144},[38,801,628],{"class":44},[38,803,631],{"class":48},[38,805,634],{"class":137},[38,807,637],{"class":48},[38,809,810,812,814,816,818,820,822],{"class":40,"line":240},[38,811,618],{"class":48},[38,813,223],{"class":137},[38,815,179],{"class":48},[38,817,161],{"class":144},[38,819,628],{"class":44},[38,821,652],{"class":137},[38,823,655],{"class":48},[38,825,826,828,831,833,836,838],{"class":40,"line":253},[38,827,618],{"class":48},[38,829,830],{"class":137},"catch",[38,832,179],{"class":48},[38,834,835],{"class":144},"err",[38,837,628],{"class":44},[38,839,205],{"class":48},[38,841,842,845,848,851,854,857,860,863],{"class":40,"line":269},[38,843,844],{"class":44},"        if",[38,846,847],{"class":48}," (err.name ",[38,849,850],{"class":44},"!==",[38,852,853],{"class":55}," 'AbortError'",[38,855,856],{"class":48},") ",[38,858,859],{"class":44},"throw",[38,861,862],{"class":48}," err  ",[38,864,865],{"class":69},"\u002F\u002F re-throw real errors\n",[38,867,868],{"class":40,"line":275},[38,869,870],{"class":48},"      })\n",[38,872,873,875,877,879,882,885,888],{"class":40,"line":284},[38,874,243],{"class":44},[38,876,246],{"class":48},[38,878,202],{"class":44},[38,880,881],{"class":48}," controller.",[38,883,884],{"class":137},"abort",[38,886,887],{"class":48},"()  ",[38,889,890],{"class":69},"\u002F\u002F cancels the in-flight request on cleanup\n",[38,892,893],{"class":40,"line":289},[38,894,660],{"class":48},[38,896,897],{"class":40,"line":295},[38,898,899],{"class":69},"  \u002F\u002F When userId changes: cleanup aborts the old request → no stale overwrite.\n",[38,901,902,904,906,908,910,912],{"class":40,"line":301},[38,903,680],{"class":44},[38,905,683],{"class":48},[38,907,687],{"class":686},[38,909,690],{"class":48},[38,911,687],{"class":686},[38,913,695],{"class":48},[38,915,916],{"class":40,"line":307},[38,917,328],{"class":48},[38,919,920],{"class":40,"line":313},[38,921,63],{"emptyLinePlaceholder":62},[38,923,924],{"class":40,"line":319},[38,925,926],{"class":69},"\u002F\u002F ALTERNATIVE: stale flag (if you can't use AbortController)\n",[38,928,929,931,933,935],{"class":40,"line":325},[38,930,17],{"class":137},[38,932,199],{"class":48},[38,934,202],{"class":44},[38,936,205],{"class":48},[38,938,940,943,946,948],{"class":40,"line":939},34,[38,941,942],{"class":44},"  let",[38,944,945],{"class":48}," isStale ",[38,947,173],{"class":44},[38,949,950],{"class":160}," false\n",[38,952,954,957,959,961,963,965,968,970,972,974,976,978,980,983,985,987,989,991],{"class":40,"line":953},35,[38,955,956],{"class":137},"  fetch",[38,958,179],{"class":48},[38,960,606],{"class":55},[38,962,558],{"class":48},[38,964,611],{"class":55},[38,966,967],{"class":48},").",[38,969,223],{"class":137},[38,971,179],{"class":48},[38,973,625],{"class":144},[38,975,628],{"class":44},[38,977,631],{"class":48},[38,979,634],{"class":137},[38,981,982],{"class":48},"()).",[38,984,223],{"class":137},[38,986,179],{"class":48},[38,988,161],{"class":144},[38,990,628],{"class":44},[38,992,205],{"class":48},[38,994,996,999,1002,1005,1008,1010,1013],{"class":40,"line":995},36,[38,997,998],{"class":44},"    if",[38,1000,1001],{"class":48}," (",[38,1003,1004],{"class":44},"!",[38,1006,1007],{"class":48},"isStale) ",[38,1009,574],{"class":137},[38,1011,1012],{"class":48},"(data)  ",[38,1014,1015],{"class":69},"\u002F\u002F only set if this effect is still current\n",[38,1017,1019],{"class":40,"line":1018},37,[38,1020,1021],{"class":48},"  })\n",[38,1023,1025,1027,1029,1031,1034,1036,1039],{"class":40,"line":1024},38,[38,1026,680],{"class":44},[38,1028,246],{"class":48},[38,1030,202],{"class":44},[38,1032,1033],{"class":48}," { isStale ",[38,1035,173],{"class":44},[38,1037,1038],{"class":160}," true",[38,1040,1041],{"class":48}," }\n",[38,1043,1045],{"class":40,"line":1044},39,[38,1046,1047],{"class":48},"}, [userId])\n",[20,1049,1051],{"id":1050},"debouncing-via-useeffect","Debouncing via useEffect",[25,1053,1055],{"filename":1054,"language":28},"debounce_effect.js",[30,1056,1058],{"className":32,"code":1057,"language":28,"meta":34,"style":34},"\u002F\u002F The canonical debounce pattern: useEffect + setTimeout + cleanup\nfunction SearchBox({ onSearch }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    if (query === '') return\n    const timeoutId = setTimeout(() => onSearch(query), 300)\n    \u002F\u002F Cleanup runs BEFORE the next effect: clears the timer if query changes\n    \u002F\u002F within 300ms → the API call only fires 300ms after the user STOPS typing.\n    return () => clearTimeout(timeoutId)\n  }, [query, onSearch])\n\n  return \u003Cinput value={query} onChange={e => setQuery(e.target.value)} placeholder=\"Search…\" \u002F>\n}\n\u002F\u002F SEQUENCE for typing \"abc\" (3 keystrokes within 300ms):\n\u002F\u002F keystroke \"a\" → effect → setTimeout(300ms)\n\u002F\u002F keystroke \"b\" → cleanup (clears timeout) → effect → setTimeout(300ms)\n\u002F\u002F keystroke \"c\" → cleanup (clears timeout) → effect → setTimeout(300ms)\n\u002F\u002F 300ms passes → onSearch(\"abc\") fires ONCE\n",[15,1059,1060,1065,1079,1106,1110,1120,1138,1165,1170,1175,1189,1194,1198,1245,1249,1254,1259,1264,1269],{"__ignoreMap":34},[38,1061,1062],{"class":40,"line":41},[38,1063,1064],{"class":69},"\u002F\u002F The canonical debounce pattern: useEffect + setTimeout + cleanup\n",[38,1066,1067,1069,1072,1074,1077],{"class":40,"line":59},[38,1068,134],{"class":44},[38,1070,1071],{"class":137}," SearchBox",[38,1073,141],{"class":48},[38,1075,1076],{"class":144},"onSearch",[38,1078,148],{"class":48},[38,1080,1081,1083,1085,1088,1090,1093,1095,1097,1099,1101,1104],{"class":40,"line":66},[38,1082,154],{"class":44},[38,1084,157],{"class":48},[38,1086,1087],{"class":160},"query",[38,1089,164],{"class":48},[38,1091,1092],{"class":160},"setQuery",[38,1094,170],{"class":48},[38,1096,173],{"class":44},[38,1098,176],{"class":137},[38,1100,179],{"class":48},[38,1102,1103],{"class":55},"''",[38,1105,185],{"class":48},[38,1107,1108],{"class":40,"line":73},[38,1109,63],{"emptyLinePlaceholder":62},[38,1111,1112,1114,1116,1118],{"class":40,"line":79},[38,1113,196],{"class":137},[38,1115,199],{"class":48},[38,1117,202],{"class":44},[38,1119,205],{"class":48},[38,1121,1122,1124,1127,1130,1133,1135],{"class":40,"line":84},[38,1123,998],{"class":44},[38,1125,1126],{"class":48}," (query ",[38,1128,1129],{"class":44},"===",[38,1131,1132],{"class":55}," ''",[38,1134,856],{"class":48},[38,1136,1137],{"class":44},"return\n",[38,1139,1140,1142,1145,1147,1150,1152,1154,1157,1160,1163],{"class":40,"line":90},[38,1141,760],{"class":44},[38,1143,1144],{"class":160}," timeoutId",[38,1146,467],{"class":44},[38,1148,1149],{"class":137}," setTimeout",[38,1151,199],{"class":48},[38,1153,202],{"class":44},[38,1155,1156],{"class":137}," onSearch",[38,1158,1159],{"class":48},"(query), ",[38,1161,1162],{"class":160},"300",[38,1164,185],{"class":48},[38,1166,1167],{"class":40,"line":96},[38,1168,1169],{"class":69},"    \u002F\u002F Cleanup runs BEFORE the next effect: clears the timer if query changes\n",[38,1171,1172],{"class":40,"line":102},[38,1173,1174],{"class":69},"    \u002F\u002F within 300ms → the API call only fires 300ms after the user STOPS typing.\n",[38,1176,1177,1179,1181,1183,1186],{"class":40,"line":108},[38,1178,243],{"class":44},[38,1180,246],{"class":48},[38,1182,202],{"class":44},[38,1184,1185],{"class":137}," clearTimeout",[38,1187,1188],{"class":48},"(timeoutId)\n",[38,1190,1191],{"class":40,"line":114},[38,1192,1193],{"class":48},"  }, [query, onSearch])\n",[38,1195,1196],{"class":40,"line":120},[38,1197,63],{"emptyLinePlaceholder":62},[38,1199,1200,1202,1204,1207,1210,1212,1215,1218,1220,1223,1226,1228,1231,1234,1237,1239,1242],{"class":40,"line":126},[38,1201,680],{"class":44},[38,1203,683],{"class":48},[38,1205,1206],{"class":686},"input",[38,1208,1209],{"class":137}," value",[38,1211,173],{"class":44},[38,1213,1214],{"class":48},"{query} ",[38,1216,1217],{"class":137},"onChange",[38,1219,173],{"class":44},[38,1221,1222],{"class":48},"{",[38,1224,1225],{"class":144},"e",[38,1227,628],{"class":44},[38,1229,1230],{"class":137}," setQuery",[38,1232,1233],{"class":48},"(e.target.value)} ",[38,1235,1236],{"class":137},"placeholder",[38,1238,173],{"class":44},[38,1240,1241],{"class":55},"\"Search…\"",[38,1243,1244],{"class":48}," \u002F>\n",[38,1246,1247],{"class":40,"line":131},[38,1248,328],{"class":48},[38,1250,1251],{"class":40,"line":151},[38,1252,1253],{"class":69},"\u002F\u002F SEQUENCE for typing \"abc\" (3 keystrokes within 300ms):\n",[38,1255,1256],{"class":40,"line":188},[38,1257,1258],{"class":69},"\u002F\u002F keystroke \"a\" → effect → setTimeout(300ms)\n",[38,1260,1261],{"class":40,"line":193},[38,1262,1263],{"class":69},"\u002F\u002F keystroke \"b\" → cleanup (clears timeout) → effect → setTimeout(300ms)\n",[38,1265,1266],{"class":40,"line":208},[38,1267,1268],{"class":69},"\u002F\u002F keystroke \"c\" → cleanup (clears timeout) → effect → setTimeout(300ms)\n",[38,1270,1271],{"class":40,"line":214},[38,1272,1273],{"class":69},"\u002F\u002F 300ms passes → onSearch(\"abc\") fires ONCE\n",[20,1275,1277],{"id":1276},"production-data-fetching-pattern","Production Data Fetching Pattern",[25,1279,1281],{"filename":1280,"language":28},"production_fetch.js",[30,1282,1284],{"className":32,"code":1283,"language":28,"meta":34,"style":34},"function useFetch(url) {\n  const [state, setState] = useState({ data: null, loading: true, error: null })\n\n  useEffect(() => {\n    if (!url) return\n\n    const controller = new AbortController()\n    setState(prev => ({ ...prev, loading: true, error: null }))\n\n    fetch(url, { signal: controller.signal })\n      .then(res => {\n        if (!res.ok) throw new Error(`HTTP ${res.status}`)\n        return res.json()\n      })\n      .then(data => setState({ data, loading: false, error: null }))\n      .catch(err => {\n        if (err.name === 'AbortError') return  \u002F\u002F ignore cancellation\n        setState(prev => ({ ...prev, loading: false, error: err.message }))\n      })\n\n    return () => controller.abort()\n  }, [url])\n\n  return state\n}\n\n\u002F\u002F Usage: const { data, loading, error } = useFetch('\u002Fapi\u002Fusers\u002F1')\n\u002F\u002F Handles: loading state, error state, cancellation on URL change,\n\u002F\u002F and avoids the stale-response race condition.\n",[15,1285,1286,1301,1340,1344,1354,1367,1371,1385,1415,1419,1426,1440,1475,1486,1490,1517,1531,1549,1571,1575,1579,1593,1598,1602,1609,1613,1617,1622,1627],{"__ignoreMap":34},[38,1287,1288,1290,1293,1295,1298],{"class":40,"line":41},[38,1289,134],{"class":44},[38,1291,1292],{"class":137}," useFetch",[38,1294,179],{"class":48},[38,1296,1297],{"class":144},"url",[38,1299,1300],{"class":48},") {\n",[38,1302,1303,1305,1307,1310,1312,1315,1317,1319,1321,1324,1326,1329,1332,1335,1337],{"class":40,"line":59},[38,1304,154],{"class":44},[38,1306,157],{"class":48},[38,1308,1309],{"class":160},"state",[38,1311,164],{"class":48},[38,1313,1314],{"class":160},"setState",[38,1316,170],{"class":48},[38,1318,173],{"class":44},[38,1320,176],{"class":137},[38,1322,1323],{"class":48},"({ data: ",[38,1325,182],{"class":160},[38,1327,1328],{"class":48},", loading: ",[38,1330,1331],{"class":160},"true",[38,1333,1334],{"class":48},", error: ",[38,1336,182],{"class":160},[38,1338,1339],{"class":48}," })\n",[38,1341,1342],{"class":40,"line":66},[38,1343,63],{"emptyLinePlaceholder":62},[38,1345,1346,1348,1350,1352],{"class":40,"line":73},[38,1347,196],{"class":137},[38,1349,199],{"class":48},[38,1351,202],{"class":44},[38,1353,205],{"class":48},[38,1355,1356,1358,1360,1362,1365],{"class":40,"line":79},[38,1357,998],{"class":44},[38,1359,1001],{"class":48},[38,1361,1004],{"class":44},[38,1363,1364],{"class":48},"url) ",[38,1366,1137],{"class":44},[38,1368,1369],{"class":40,"line":84},[38,1370,63],{"emptyLinePlaceholder":62},[38,1372,1373,1375,1377,1379,1381,1383],{"class":40,"line":90},[38,1374,760],{"class":44},[38,1376,763],{"class":160},[38,1378,467],{"class":44},[38,1380,768],{"class":44},[38,1382,771],{"class":137},[38,1384,774],{"class":48},[38,1386,1387,1390,1392,1395,1397,1400,1403,1406,1408,1410,1412],{"class":40,"line":96},[38,1388,1389],{"class":137},"    setState",[38,1391,179],{"class":48},[38,1393,1394],{"class":144},"prev",[38,1396,628],{"class":44},[38,1398,1399],{"class":48}," ({ ",[38,1401,1402],{"class":44},"...",[38,1404,1405],{"class":48},"prev, loading: ",[38,1407,1331],{"class":160},[38,1409,1334],{"class":48},[38,1411,182],{"class":160},[38,1413,1414],{"class":48}," }))\n",[38,1416,1417],{"class":40,"line":102},[38,1418,63],{"emptyLinePlaceholder":62},[38,1420,1421,1423],{"class":40,"line":108},[38,1422,601],{"class":137},[38,1424,1425],{"class":48},"(url, { signal: controller.signal })\n",[38,1427,1428,1430,1432,1434,1436,1438],{"class":40,"line":114},[38,1429,618],{"class":48},[38,1431,223],{"class":137},[38,1433,179],{"class":48},[38,1435,625],{"class":144},[38,1437,628],{"class":44},[38,1439,205],{"class":48},[38,1441,1442,1444,1446,1448,1451,1453,1455,1458,1460,1463,1465,1468,1471,1473],{"class":40,"line":120},[38,1443,844],{"class":44},[38,1445,1001],{"class":48},[38,1447,1004],{"class":44},[38,1449,1450],{"class":48},"res.ok) ",[38,1452,859],{"class":44},[38,1454,768],{"class":44},[38,1456,1457],{"class":137}," Error",[38,1459,179],{"class":48},[38,1461,1462],{"class":55},"`HTTP ${",[38,1464,625],{"class":48},[38,1466,1467],{"class":55},".",[38,1469,1470],{"class":48},"status",[38,1472,611],{"class":55},[38,1474,185],{"class":48},[38,1476,1477,1480,1482,1484],{"class":40,"line":126},[38,1478,1479],{"class":44},"        return",[38,1481,631],{"class":48},[38,1483,634],{"class":137},[38,1485,774],{"class":48},[38,1487,1488],{"class":40,"line":131},[38,1489,870],{"class":48},[38,1491,1492,1494,1496,1498,1500,1502,1505,1508,1511,1513,1515],{"class":40,"line":151},[38,1493,618],{"class":48},[38,1495,223],{"class":137},[38,1497,179],{"class":48},[38,1499,161],{"class":144},[38,1501,628],{"class":44},[38,1503,1504],{"class":137}," setState",[38,1506,1507],{"class":48},"({ data, loading: ",[38,1509,1510],{"class":160},"false",[38,1512,1334],{"class":48},[38,1514,182],{"class":160},[38,1516,1414],{"class":48},[38,1518,1519,1521,1523,1525,1527,1529],{"class":40,"line":188},[38,1520,618],{"class":48},[38,1522,830],{"class":137},[38,1524,179],{"class":48},[38,1526,835],{"class":144},[38,1528,628],{"class":44},[38,1530,205],{"class":48},[38,1532,1533,1535,1537,1539,1541,1543,1546],{"class":40,"line":193},[38,1534,844],{"class":44},[38,1536,847],{"class":48},[38,1538,1129],{"class":44},[38,1540,853],{"class":55},[38,1542,856],{"class":48},[38,1544,1545],{"class":44},"return",[38,1547,1548],{"class":69},"  \u002F\u002F ignore cancellation\n",[38,1550,1551,1554,1556,1558,1560,1562,1564,1566,1568],{"class":40,"line":208},[38,1552,1553],{"class":137},"        setState",[38,1555,179],{"class":48},[38,1557,1394],{"class":144},[38,1559,628],{"class":44},[38,1561,1399],{"class":48},[38,1563,1402],{"class":44},[38,1565,1405],{"class":48},[38,1567,1510],{"class":160},[38,1569,1570],{"class":48},", error: err.message }))\n",[38,1572,1573],{"class":40,"line":214},[38,1574,870],{"class":48},[38,1576,1577],{"class":40,"line":229},[38,1578,63],{"emptyLinePlaceholder":62},[38,1580,1581,1583,1585,1587,1589,1591],{"class":40,"line":234},[38,1582,243],{"class":44},[38,1584,246],{"class":48},[38,1586,202],{"class":44},[38,1588,881],{"class":48},[38,1590,884],{"class":137},[38,1592,774],{"class":48},[38,1594,1595],{"class":40,"line":240},[38,1596,1597],{"class":48},"  }, [url])\n",[38,1599,1600],{"class":40,"line":253},[38,1601,63],{"emptyLinePlaceholder":62},[38,1603,1604,1606],{"class":40,"line":269},[38,1605,680],{"class":44},[38,1607,1608],{"class":48}," state\n",[38,1610,1611],{"class":40,"line":275},[38,1612,328],{"class":48},[38,1614,1615],{"class":40,"line":284},[38,1616,63],{"emptyLinePlaceholder":62},[38,1618,1619],{"class":40,"line":289},[38,1620,1621],{"class":69},"\u002F\u002F Usage: const { data, loading, error } = useFetch('\u002Fapi\u002Fusers\u002F1')\n",[38,1623,1624],{"class":40,"line":295},[38,1625,1626],{"class":69},"\u002F\u002F Handles: loading state, error state, cancellation on URL change,\n",[38,1628,1629],{"class":40,"line":301},[38,1630,1631],{"class":69},"\u002F\u002F and avoids the stale-response race condition.\n",[20,1633,1635],{"id":1634},"anti-pattern-effects-for-derived-state","Anti-Pattern: Effects for Derived State",[25,1637,1639],{"filename":1638,"language":28},"anti_pattern_derived.js",[30,1640,1642],{"className":32,"code":1641,"language":28,"meta":34,"style":34},"\u002F\u002F ANTI-PATTERN: using an effect to compute a value from props\u002Fstate\nfunction BadProductList({ products, filter }) {\n  const [filtered, setFiltered] = useState(products)\n  useEffect(() => {\n    setFiltered(products.filter(p => p.category === filter))\n  }, [products, filter])\n  \u002F\u002F This causes an EXTRA render: render → effect → setState → re-render.\n  \u002F\u002F The user sees a flash of the unfiltered list before the effect runs.\n\n  return \u003Cul>{filtered.map(p => \u003Cli key={p.id}>{p.name}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n\n\u002F\u002F PRODUCTION: compute during render (React handles it efficiently)\nfunction GoodProductList({ products, filter }) {\n  const filtered = useMemo(\n    () => products.filter(p => p.category === filter),\n    [products, filter]\n  )\n  \u002F\u002F No extra render. useMemo avoids recomputing on unrelated re-renders.\n  \u002F\u002F For truly simple derivations, even useMemo is optional — just:\n  \u002F\u002F const filtered = products.filter(p => p.category === filter)\n  return \u003Cul>{filtered.map(p => \u003Cli key={p.id}>{p.name}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n",[15,1643,1644,1649,1668,1691,1701,1726,1731,1736,1741,1745,1788,1792,1796,1801,1818,1832,1857,1862,1867,1872,1877,1882,1918],{"__ignoreMap":34},[38,1645,1646],{"class":40,"line":41},[38,1647,1648],{"class":69},"\u002F\u002F ANTI-PATTERN: using an effect to compute a value from props\u002Fstate\n",[38,1650,1651,1653,1656,1658,1661,1663,1666],{"class":40,"line":59},[38,1652,134],{"class":44},[38,1654,1655],{"class":137}," BadProductList",[38,1657,141],{"class":48},[38,1659,1660],{"class":144},"products",[38,1662,164],{"class":48},[38,1664,1665],{"class":144},"filter",[38,1667,148],{"class":48},[38,1669,1670,1672,1674,1677,1679,1682,1684,1686,1688],{"class":40,"line":66},[38,1671,154],{"class":44},[38,1673,157],{"class":48},[38,1675,1676],{"class":160},"filtered",[38,1678,164],{"class":48},[38,1680,1681],{"class":160},"setFiltered",[38,1683,170],{"class":48},[38,1685,173],{"class":44},[38,1687,176],{"class":137},[38,1689,1690],{"class":48},"(products)\n",[38,1692,1693,1695,1697,1699],{"class":40,"line":73},[38,1694,196],{"class":137},[38,1696,199],{"class":48},[38,1698,202],{"class":44},[38,1700,205],{"class":48},[38,1702,1703,1706,1709,1711,1713,1716,1718,1721,1723],{"class":40,"line":79},[38,1704,1705],{"class":137},"    setFiltered",[38,1707,1708],{"class":48},"(products.",[38,1710,1665],{"class":137},[38,1712,179],{"class":48},[38,1714,1715],{"class":144},"p",[38,1717,628],{"class":44},[38,1719,1720],{"class":48}," p.category ",[38,1722,1129],{"class":44},[38,1724,1725],{"class":48}," filter))\n",[38,1727,1728],{"class":40,"line":84},[38,1729,1730],{"class":48},"  }, [products, filter])\n",[38,1732,1733],{"class":40,"line":90},[38,1734,1735],{"class":69},"  \u002F\u002F This causes an EXTRA render: render → effect → setState → re-render.\n",[38,1737,1738],{"class":40,"line":96},[38,1739,1740],{"class":69},"  \u002F\u002F The user sees a flash of the unfiltered list before the effect runs.\n",[38,1742,1743],{"class":40,"line":102},[38,1744,63],{"emptyLinePlaceholder":62},[38,1746,1747,1749,1751,1754,1757,1760,1762,1764,1766,1768,1771,1774,1776,1779,1781,1784,1786],{"class":40,"line":108},[38,1748,680],{"class":44},[38,1750,683],{"class":48},[38,1752,1753],{"class":686},"ul",[38,1755,1756],{"class":48},">{filtered.",[38,1758,1759],{"class":137},"map",[38,1761,179],{"class":48},[38,1763,1715],{"class":144},[38,1765,628],{"class":44},[38,1767,683],{"class":48},[38,1769,1770],{"class":686},"li",[38,1772,1773],{"class":137}," key",[38,1775,173],{"class":44},[38,1777,1778],{"class":48},"{p.id}>{p.name}\u003C\u002F",[38,1780,1770],{"class":686},[38,1782,1783],{"class":48},">)}\u003C\u002F",[38,1785,1753],{"class":686},[38,1787,695],{"class":48},[38,1789,1790],{"class":40,"line":114},[38,1791,328],{"class":48},[38,1793,1794],{"class":40,"line":120},[38,1795,63],{"emptyLinePlaceholder":62},[38,1797,1798],{"class":40,"line":126},[38,1799,1800],{"class":69},"\u002F\u002F PRODUCTION: compute during render (React handles it efficiently)\n",[38,1802,1803,1805,1808,1810,1812,1814,1816],{"class":40,"line":131},[38,1804,134],{"class":44},[38,1806,1807],{"class":137}," GoodProductList",[38,1809,141],{"class":48},[38,1811,1660],{"class":144},[38,1813,164],{"class":48},[38,1815,1665],{"class":144},[38,1817,148],{"class":48},[38,1819,1820,1822,1825,1827,1829],{"class":40,"line":151},[38,1821,154],{"class":44},[38,1823,1824],{"class":160}," filtered",[38,1826,467],{"class":44},[38,1828,470],{"class":137},[38,1830,1831],{"class":48},"(\n",[38,1833,1834,1837,1839,1842,1844,1846,1848,1850,1852,1854],{"class":40,"line":188},[38,1835,1836],{"class":48},"    () ",[38,1838,202],{"class":44},[38,1840,1841],{"class":48}," products.",[38,1843,1665],{"class":137},[38,1845,179],{"class":48},[38,1847,1715],{"class":144},[38,1849,628],{"class":44},[38,1851,1720],{"class":48},[38,1853,1129],{"class":44},[38,1855,1856],{"class":48}," filter),\n",[38,1858,1859],{"class":40,"line":193},[38,1860,1861],{"class":48},"    [products, filter]\n",[38,1863,1864],{"class":40,"line":208},[38,1865,1866],{"class":48},"  )\n",[38,1868,1869],{"class":40,"line":214},[38,1870,1871],{"class":69},"  \u002F\u002F No extra render. useMemo avoids recomputing on unrelated re-renders.\n",[38,1873,1874],{"class":40,"line":229},[38,1875,1876],{"class":69},"  \u002F\u002F For truly simple derivations, even useMemo is optional — just:\n",[38,1878,1879],{"class":40,"line":234},[38,1880,1881],{"class":69},"  \u002F\u002F const filtered = products.filter(p => p.category === filter)\n",[38,1883,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916],{"class":40,"line":240},[38,1885,680],{"class":44},[38,1887,683],{"class":48},[38,1889,1753],{"class":686},[38,1891,1756],{"class":48},[38,1893,1759],{"class":137},[38,1895,179],{"class":48},[38,1897,1715],{"class":144},[38,1899,628],{"class":44},[38,1901,683],{"class":48},[38,1903,1770],{"class":686},[38,1905,1773],{"class":137},[38,1907,173],{"class":44},[38,1909,1778],{"class":48},[38,1911,1770],{"class":686},[38,1913,1783],{"class":48},[38,1915,1753],{"class":686},[38,1917,695],{"class":48},[38,1919,1920],{"class":40,"line":253},[38,1921,328],{"class":48},[20,1923,1925],{"id":1924},"tips-tricks","💡 Tips & Tricks",[25,1927,1929],{"filename":1928,"language":28},"tips.js",[30,1930,1932],{"className":32,"code":1931,"language":28,"meta":34,"style":34},"\u002F\u002F [Idiom] Think of useEffect as \"sync this external thing with these props\u002Fstate,\"\n\u002F\u002F not as a lifecycle method. It's NOT componentDidMount + componentDidUpdate.\n\u002F\u002F It's: \"after render, make sure X is in sync with [deps].\"\n\n\u002F\u002F [Debug] If your effect runs more often than expected, check for inline\n\u002F\u002F objects\u002Ffunctions in the deps array. {} !== {} every render → infinite effect.\n\n\u002F\u002F [Performance] For expensive derivations from props\u002Fstate, use useMemo during\n\u002F\u002F render — NOT useEffect + setState. Effects for derived state cause an extra\n\u002F\u002F render and a flash of stale data.\n\n\u002F\u002F [Idiom] The cleanup function is not optional for effects that create\n\u002F\u002F subscriptions, timers, or network requests. Always return a cleanup that\n\u002F\u002F tears down what the effect set up.\n\n\u002F\u002F [Debug] eslint-plugin-react-hooks' exhaustive-deps rule catches missing\n\u002F\u002F dependencies. Don't silence it with eslint-disable — fix the underlying issue\n\u002F\u002F (usually by memoizing the dep with useCallback\u002FuseMemo, or restructuring).\n",[15,1933,1934,1939,1944,1949,1953,1958,1963,1967,1972,1977,1982,1986,1991,1996,2001,2005,2010,2015],{"__ignoreMap":34},[38,1935,1936],{"class":40,"line":41},[38,1937,1938],{"class":69},"\u002F\u002F [Idiom] Think of useEffect as \"sync this external thing with these props\u002Fstate,\"\n",[38,1940,1941],{"class":40,"line":59},[38,1942,1943],{"class":69},"\u002F\u002F not as a lifecycle method. It's NOT componentDidMount + componentDidUpdate.\n",[38,1945,1946],{"class":40,"line":66},[38,1947,1948],{"class":69},"\u002F\u002F It's: \"after render, make sure X is in sync with [deps].\"\n",[38,1950,1951],{"class":40,"line":73},[38,1952,63],{"emptyLinePlaceholder":62},[38,1954,1955],{"class":40,"line":79},[38,1956,1957],{"class":69},"\u002F\u002F [Debug] If your effect runs more often than expected, check for inline\n",[38,1959,1960],{"class":40,"line":84},[38,1961,1962],{"class":69},"\u002F\u002F objects\u002Ffunctions in the deps array. {} !== {} every render → infinite effect.\n",[38,1964,1965],{"class":40,"line":90},[38,1966,63],{"emptyLinePlaceholder":62},[38,1968,1969],{"class":40,"line":96},[38,1970,1971],{"class":69},"\u002F\u002F [Performance] For expensive derivations from props\u002Fstate, use useMemo during\n",[38,1973,1974],{"class":40,"line":102},[38,1975,1976],{"class":69},"\u002F\u002F render — NOT useEffect + setState. Effects for derived state cause an extra\n",[38,1978,1979],{"class":40,"line":108},[38,1980,1981],{"class":69},"\u002F\u002F render and a flash of stale data.\n",[38,1983,1984],{"class":40,"line":114},[38,1985,63],{"emptyLinePlaceholder":62},[38,1987,1988],{"class":40,"line":120},[38,1989,1990],{"class":69},"\u002F\u002F [Idiom] The cleanup function is not optional for effects that create\n",[38,1992,1993],{"class":40,"line":126},[38,1994,1995],{"class":69},"\u002F\u002F subscriptions, timers, or network requests. Always return a cleanup that\n",[38,1997,1998],{"class":40,"line":131},[38,1999,2000],{"class":69},"\u002F\u002F tears down what the effect set up.\n",[38,2002,2003],{"class":40,"line":151},[38,2004,63],{"emptyLinePlaceholder":62},[38,2006,2007],{"class":40,"line":188},[38,2008,2009],{"class":69},"\u002F\u002F [Debug] eslint-plugin-react-hooks' exhaustive-deps rule catches missing\n",[38,2011,2012],{"class":40,"line":193},[38,2013,2014],{"class":69},"\u002F\u002F dependencies. Don't silence it with eslint-disable — fix the underlying issue\n",[38,2016,2017],{"class":40,"line":208},[38,2018,2019],{"class":69},"\u002F\u002F (usually by memoizing the dep with useCallback\u002FuseMemo, or restructuring).\n",[20,2021,2023],{"id":2022},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[25,2025,2027],{"filename":2026,"language":28},"edge_cases.js",[30,2028,2030],{"className":32,"code":2029,"language":28,"meta":34,"style":34},"\u002F\u002F [Gotcha] useEffect runs AFTER paint. If you need to measure\u002Fmodify DOM BEFORE\n\u002F\u002F paint (to avoid flicker), use useLayoutEffect instead. It runs synchronously\n\u002F\u002F after DOM mutations but before the browser paints.\n\n\u002F\u002F [Gotcha] Empty deps [] means \"run once on mount\" — but the closure inside\n\u002F\u002F captures values from the FIRST render forever. If you reference state\u002Fprops\n\u002F\u002F inside, they'll be stale. Use refs or functional updates to access fresh values.\n\n\u002F\u002F [Gotcha] The cleanup function runs on unmount AND before the next effect.\n\u002F\u002F It does NOT only run on unmount. If your deps change 5 times, cleanup runs\n\u002F\u002F 5 times (before each new effect) plus once on unmount.\n\n\u002F\u002F [Gotcha] StrictMode double-invokes effects in dev: mount → unmount → mount.\n\u002F\u002F An effect that opens a WebSocket without cleanup → TWO connections in dev.\n\u002F\u002F Always implement cleanup, even if you think the effect runs \"once.\"\n\n\u002F\u002F [Gotcha] Calling setState inside an effect that depends on that same state\n\u002F\u002F creates an infinite loop: effect → setState → re-render → effect → ...\n\u002F\u002F Break the cycle by removing the state from deps, or use a ref instead.\n\n\u002F\u002F [Safety] Never call a setter from a different component's effect. React throws\n\u002F\u002F \"Cannot update a component while rendering a different component.\" If two\n\u002F\u002F components need to share state, lift it up to a common parent or use context.\n",[15,2031,2032,2037,2042,2047,2051,2056,2061,2066,2070,2075,2080,2085,2089,2094,2099,2104,2108,2113,2118,2123,2127,2132,2137],{"__ignoreMap":34},[38,2033,2034],{"class":40,"line":41},[38,2035,2036],{"class":69},"\u002F\u002F [Gotcha] useEffect runs AFTER paint. If you need to measure\u002Fmodify DOM BEFORE\n",[38,2038,2039],{"class":40,"line":59},[38,2040,2041],{"class":69},"\u002F\u002F paint (to avoid flicker), use useLayoutEffect instead. It runs synchronously\n",[38,2043,2044],{"class":40,"line":66},[38,2045,2046],{"class":69},"\u002F\u002F after DOM mutations but before the browser paints.\n",[38,2048,2049],{"class":40,"line":73},[38,2050,63],{"emptyLinePlaceholder":62},[38,2052,2053],{"class":40,"line":79},[38,2054,2055],{"class":69},"\u002F\u002F [Gotcha] Empty deps [] means \"run once on mount\" — but the closure inside\n",[38,2057,2058],{"class":40,"line":84},[38,2059,2060],{"class":69},"\u002F\u002F captures values from the FIRST render forever. If you reference state\u002Fprops\n",[38,2062,2063],{"class":40,"line":90},[38,2064,2065],{"class":69},"\u002F\u002F inside, they'll be stale. Use refs or functional updates to access fresh values.\n",[38,2067,2068],{"class":40,"line":96},[38,2069,63],{"emptyLinePlaceholder":62},[38,2071,2072],{"class":40,"line":102},[38,2073,2074],{"class":69},"\u002F\u002F [Gotcha] The cleanup function runs on unmount AND before the next effect.\n",[38,2076,2077],{"class":40,"line":108},[38,2078,2079],{"class":69},"\u002F\u002F It does NOT only run on unmount. If your deps change 5 times, cleanup runs\n",[38,2081,2082],{"class":40,"line":114},[38,2083,2084],{"class":69},"\u002F\u002F 5 times (before each new effect) plus once on unmount.\n",[38,2086,2087],{"class":40,"line":120},[38,2088,63],{"emptyLinePlaceholder":62},[38,2090,2091],{"class":40,"line":126},[38,2092,2093],{"class":69},"\u002F\u002F [Gotcha] StrictMode double-invokes effects in dev: mount → unmount → mount.\n",[38,2095,2096],{"class":40,"line":131},[38,2097,2098],{"class":69},"\u002F\u002F An effect that opens a WebSocket without cleanup → TWO connections in dev.\n",[38,2100,2101],{"class":40,"line":151},[38,2102,2103],{"class":69},"\u002F\u002F Always implement cleanup, even if you think the effect runs \"once.\"\n",[38,2105,2106],{"class":40,"line":188},[38,2107,63],{"emptyLinePlaceholder":62},[38,2109,2110],{"class":40,"line":193},[38,2111,2112],{"class":69},"\u002F\u002F [Gotcha] Calling setState inside an effect that depends on that same state\n",[38,2114,2115],{"class":40,"line":208},[38,2116,2117],{"class":69},"\u002F\u002F creates an infinite loop: effect → setState → re-render → effect → ...\n",[38,2119,2120],{"class":40,"line":214},[38,2121,2122],{"class":69},"\u002F\u002F Break the cycle by removing the state from deps, or use a ref instead.\n",[38,2124,2125],{"class":40,"line":229},[38,2126,63],{"emptyLinePlaceholder":62},[38,2128,2129],{"class":40,"line":234},[38,2130,2131],{"class":69},"\u002F\u002F [Safety] Never call a setter from a different component's effect. React throws\n",[38,2133,2134],{"class":40,"line":240},[38,2135,2136],{"class":69},"\u002F\u002F \"Cannot update a component while rendering a different component.\" If two\n",[38,2138,2139],{"class":40,"line":253},[38,2140,2141],{"class":69},"\u002F\u002F components need to share state, lift it up to a common parent or use context.\n",[20,2143,2145],{"id":2144},"spot-the-bug","🧠 Spot the Bug",[1715,2147,2148],{},"A component fetches data on mount but the response seems to never arrive — the loading spinner stays forever, even though the network tab shows the response:",[25,2150,2152],{"filename":2151,"language":28},"spot_the_bug.js",[30,2153,2155],{"className":32,"code":2154,"language":28,"meta":34,"style":34},"function Profile({ userId }) {\n  const [profile, setProfile] = useState(null)\n\n  useEffect(() => {\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n      .then(res => res.json())\n      .then(setProfile)\n  }, [])  \u002F\u002F ← empty deps\n\n  if (!profile) return \u003CSpinner \u002F>\n  return \u003Cdiv>{profile.name}\u003C\u002Fdiv>\n}\n",[15,2156,2157,2170,2194,2198,2208,2222,2240,2249,2257,2261,2282,2297],{"__ignoreMap":34},[38,2158,2159,2161,2164,2166,2168],{"class":40,"line":41},[38,2160,134],{"class":44},[38,2162,2163],{"class":137}," Profile",[38,2165,141],{"class":48},[38,2167,558],{"class":144},[38,2169,148],{"class":48},[38,2171,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192],{"class":40,"line":59},[38,2173,154],{"class":44},[38,2175,157],{"class":48},[38,2177,569],{"class":160},[38,2179,164],{"class":48},[38,2181,574],{"class":160},[38,2183,170],{"class":48},[38,2185,173],{"class":44},[38,2187,176],{"class":137},[38,2189,179],{"class":48},[38,2191,182],{"class":160},[38,2193,185],{"class":48},[38,2195,2196],{"class":40,"line":66},[38,2197,63],{"emptyLinePlaceholder":62},[38,2199,2200,2202,2204,2206],{"class":40,"line":73},[38,2201,196],{"class":137},[38,2203,199],{"class":48},[38,2205,202],{"class":44},[38,2207,205],{"class":48},[38,2209,2210,2212,2214,2216,2218,2220],{"class":40,"line":79},[38,2211,601],{"class":137},[38,2213,179],{"class":48},[38,2215,606],{"class":55},[38,2217,558],{"class":48},[38,2219,611],{"class":55},[38,2221,185],{"class":48},[38,2223,2224,2226,2228,2230,2232,2234,2236,2238],{"class":40,"line":84},[38,2225,618],{"class":48},[38,2227,223],{"class":137},[38,2229,179],{"class":48},[38,2231,625],{"class":144},[38,2233,628],{"class":44},[38,2235,631],{"class":48},[38,2237,634],{"class":137},[38,2239,637],{"class":48},[38,2241,2242,2244,2246],{"class":40,"line":90},[38,2243,618],{"class":48},[38,2245,223],{"class":137},[38,2247,2248],{"class":48},"(setProfile)\n",[38,2250,2251,2254],{"class":40,"line":96},[38,2252,2253],{"class":48},"  }, [])  ",[38,2255,2256],{"class":69},"\u002F\u002F ← empty deps\n",[38,2258,2259],{"class":40,"line":102},[38,2260,63],{"emptyLinePlaceholder":62},[38,2262,2263,2266,2268,2270,2273,2275,2277,2280],{"class":40,"line":108},[38,2264,2265],{"class":44},"  if",[38,2267,1001],{"class":48},[38,2269,1004],{"class":44},[38,2271,2272],{"class":48},"profile) ",[38,2274,1545],{"class":44},[38,2276,683],{"class":48},[38,2278,2279],{"class":160},"Spinner",[38,2281,1244],{"class":48},[38,2283,2284,2286,2288,2290,2293,2295],{"class":40,"line":114},[38,2285,680],{"class":44},[38,2287,683],{"class":48},[38,2289,687],{"class":686},[38,2291,2292],{"class":48},">{profile.name}\u003C\u002F",[38,2294,687],{"class":686},[38,2296,695],{"class":48},[38,2298,2299],{"class":40,"line":120},[38,2300,328],{"class":48},[2302,2303,2304,2308,2342,2351],"details",{},[2305,2306,2307],"summary",{},"Answer",[1715,2309,2310,2311,2314,2315,2319,2320,2322,2323,2325,2326,2329,2330,2333,2334,2338,2339,2341],{},"The dependency array is ",[15,2312,2313],{},"[]"," (empty), meaning the effect runs ",[2316,2317,2318],"strong",{},"only on mount"," and captures ",[15,2321,558],{}," from the first render. If ",[15,2324,558],{}," changes, the effect ",[2316,2327,2328],{},"never re-runs"," — the component shows stale data (or keeps loading if the first fetch hasn't resolved yet for a different user). The empty deps also cause a ",[2316,2331,2332],{},"stale closure",": even if the fetch succeeds, it fetches the ",[2335,2336,2337],"em",{},"original"," ",[15,2340,558],{},", not the current one.",[1715,2343,2344,2347,2348,2350],{},[2316,2345,2346],{},"Fix",": include ",[15,2349,558],{}," in the dependency array:",[30,2352,2354],{"className":32,"code":2353,"language":28,"meta":34,"style":34},"useEffect(() => {\n  const controller = new AbortController()\n  fetch(`\u002Fapi\u002Fusers\u002F${userId}`, { signal: controller.signal })\n    .then(res => res.json())\n    .then(setProfile)\n    .catch(err => { if (err.name !== 'AbortError') throw err })\n  return () => controller.abort()\n}, [userId])  \u002F\u002F re-runs when userId changes, with cancellation\n",[15,2355,2356,2366,2380,2394,2413,2421,2451,2465],{"__ignoreMap":34},[38,2357,2358,2360,2362,2364],{"class":40,"line":41},[38,2359,17],{"class":137},[38,2361,199],{"class":48},[38,2363,202],{"class":44},[38,2365,205],{"class":48},[38,2367,2368,2370,2372,2374,2376,2378],{"class":40,"line":59},[38,2369,154],{"class":44},[38,2371,763],{"class":160},[38,2373,467],{"class":44},[38,2375,768],{"class":44},[38,2377,771],{"class":137},[38,2379,774],{"class":48},[38,2381,2382,2384,2386,2388,2390,2392],{"class":40,"line":66},[38,2383,956],{"class":137},[38,2385,179],{"class":48},[38,2387,606],{"class":55},[38,2389,558],{"class":48},[38,2391,611],{"class":55},[38,2393,789],{"class":48},[38,2395,2396,2399,2401,2403,2405,2407,2409,2411],{"class":40,"line":73},[38,2397,2398],{"class":48},"    .",[38,2400,223],{"class":137},[38,2402,179],{"class":48},[38,2404,625],{"class":144},[38,2406,628],{"class":44},[38,2408,631],{"class":48},[38,2410,634],{"class":137},[38,2412,637],{"class":48},[38,2414,2415,2417,2419],{"class":40,"line":79},[38,2416,2398],{"class":48},[38,2418,223],{"class":137},[38,2420,2248],{"class":48},[38,2422,2423,2425,2427,2429,2431,2433,2435,2438,2440,2442,2444,2446,2448],{"class":40,"line":84},[38,2424,2398],{"class":48},[38,2426,830],{"class":137},[38,2428,179],{"class":48},[38,2430,835],{"class":144},[38,2432,628],{"class":44},[38,2434,370],{"class":48},[38,2436,2437],{"class":44},"if",[38,2439,847],{"class":48},[38,2441,850],{"class":44},[38,2443,853],{"class":55},[38,2445,856],{"class":48},[38,2447,859],{"class":44},[38,2449,2450],{"class":48}," err })\n",[38,2452,2453,2455,2457,2459,2461,2463],{"class":40,"line":90},[38,2454,680],{"class":44},[38,2456,246],{"class":48},[38,2458,202],{"class":44},[38,2460,881],{"class":48},[38,2462,884],{"class":137},[38,2464,774],{"class":48},[38,2466,2467,2470],{"class":40,"line":96},[38,2468,2469],{"class":48},"}, [userId])  ",[38,2471,2472],{"class":69},"\u002F\u002F re-runs when userId changes, with cancellation\n",[20,2474,2476],{"id":2475},"key-takeaways","Key Takeaways",[25,2478,2480],{"filename":2479,"language":28},"key_takeaways.js",[30,2481,2483],{"className":32,"code":2482,"language":28,"meta":34,"style":34},"\u002F\u002F 1. useEffect runs AFTER paint. deps=[a,b] → runs on mount + when a or b\n\u002F\u002F    changes (Object.is comparison). deps=[] → mount only. deps omitted → every render.\n\n\u002F\u002F 2. Cleanup runs BEFORE the next effect AND on unmount — not just unmount.\n\u002F\u002F    Always clean up subscriptions, timers, and network requests.\n\n\u002F\u002F 3. Deps compare with Object.is — inline objects\u002Ffunctions create new refs\n\u002F\u002F    every render → effect runs every time. Use useMemo\u002FuseCallback to stabilize.\n\n\u002F\u002F 4. Async effects need cancellation (AbortController or stale flag) to prevent\n\u002F\u002F    race conditions where a stale response overwrites fresh data.\n\n\u002F\u002F 5. Don't use effects for derived state — compute during render (useMemo or\n\u002F\u002F    inline). Effects for derived state cause an extra render + flash of stale data.\n\n\u002F\u002F 6. Empty deps [] captures stale values forever. If you reference props\u002Fstate\n\u002F\u002F    inside, include them in deps, or use refs\u002Ffunctional updates for fresh access.\n",[15,2484,2485,2490,2495,2499,2504,2509,2513,2518,2523,2527,2532,2537,2541,2546,2551,2555,2560],{"__ignoreMap":34},[38,2486,2487],{"class":40,"line":41},[38,2488,2489],{"class":69},"\u002F\u002F 1. useEffect runs AFTER paint. deps=[a,b] → runs on mount + when a or b\n",[38,2491,2492],{"class":40,"line":59},[38,2493,2494],{"class":69},"\u002F\u002F    changes (Object.is comparison). deps=[] → mount only. deps omitted → every render.\n",[38,2496,2497],{"class":40,"line":66},[38,2498,63],{"emptyLinePlaceholder":62},[38,2500,2501],{"class":40,"line":73},[38,2502,2503],{"class":69},"\u002F\u002F 2. Cleanup runs BEFORE the next effect AND on unmount — not just unmount.\n",[38,2505,2506],{"class":40,"line":79},[38,2507,2508],{"class":69},"\u002F\u002F    Always clean up subscriptions, timers, and network requests.\n",[38,2510,2511],{"class":40,"line":84},[38,2512,63],{"emptyLinePlaceholder":62},[38,2514,2515],{"class":40,"line":90},[38,2516,2517],{"class":69},"\u002F\u002F 3. Deps compare with Object.is — inline objects\u002Ffunctions create new refs\n",[38,2519,2520],{"class":40,"line":96},[38,2521,2522],{"class":69},"\u002F\u002F    every render → effect runs every time. Use useMemo\u002FuseCallback to stabilize.\n",[38,2524,2525],{"class":40,"line":102},[38,2526,63],{"emptyLinePlaceholder":62},[38,2528,2529],{"class":40,"line":108},[38,2530,2531],{"class":69},"\u002F\u002F 4. Async effects need cancellation (AbortController or stale flag) to prevent\n",[38,2533,2534],{"class":40,"line":114},[38,2535,2536],{"class":69},"\u002F\u002F    race conditions where a stale response overwrites fresh data.\n",[38,2538,2539],{"class":40,"line":120},[38,2540,63],{"emptyLinePlaceholder":62},[38,2542,2543],{"class":40,"line":126},[38,2544,2545],{"class":69},"\u002F\u002F 5. Don't use effects for derived state — compute during render (useMemo or\n",[38,2547,2548],{"class":40,"line":131},[38,2549,2550],{"class":69},"\u002F\u002F    inline). Effects for derived state cause an extra render + flash of stale data.\n",[38,2552,2553],{"class":40,"line":151},[38,2554,63],{"emptyLinePlaceholder":62},[38,2556,2557],{"class":40,"line":188},[38,2558,2559],{"class":69},"\u002F\u002F 6. Empty deps [] captures stale values forever. If you reference props\u002Fstate\n",[38,2561,2562],{"class":40,"line":193},[38,2563,2564],{"class":69},"\u002F\u002F    inside, include them in deps, or use refs\u002Ffunctional updates for fresh access.\n",[2566,2567,2568],"style",{},"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 .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}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":34,"searchDepth":59,"depth":59,"links":2570},[2571,2572,2573,2574,2575,2576,2577,2578,2579,2580],{"id":22,"depth":59,"text":23},{"id":331,"depth":59,"text":332},{"id":532,"depth":59,"text":533},{"id":1050,"depth":59,"text":1051},{"id":1276,"depth":59,"text":1277},{"id":1634,"depth":59,"text":1635},{"id":1924,"depth":59,"text":1925},{"id":2022,"depth":59,"text":2023},{"id":2144,"depth":59,"text":2145},{"id":2475,"depth":59,"text":2476},"Effect lifecycle, dependency array mechanics, cleanup functions, async race conditions, stale closures, and production data-fetching patterns with AbortController. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F06-useeffect-and-lifecycle",{"title":5,"description":2581},"react\u002F06-useeffect-and-lifecycle","O8MOXL5zb_LFjJJjBQMjGJ3Lxrqk1WhyctX5gIoJ8R4",1789924652031]