[{"data":1,"prerenderedAt":1252},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-useeffect-hook-complete-guide":3},{"id":4,"title":5,"body":6,"description":1237,"difficulty":1238,"extension":1239,"framework":1240,"frameworkSlug":1241,"meta":1242,"navigation":1243,"order":76,"path":1244,"qaPath":1245,"seo":1246,"stem":1247,"subtopic":20,"topic":1248,"topicSlug":1249,"updated":1250,"__hash__":1251},"blog\u002Fblog\u002Freact-useeffect-hook-complete-guide.md","React useEffect Hook — A Complete Guide to Effects, Dependencies & Cleanup",{"type":7,"value":8,"toc":1223},"minimark",[9,14,35,39,46,99,105,109,120,151,216,227,232,239,276,290,337,344,348,359,413,445,458,462,468,488,540,554,558,565,717,720,724,731,794,805,901,905,908,914,966,972,1037,1044,1048,1083,1195,1199,1219],[10,11,13],"h2",{"id":12},"a-complete-guide-to-useeffect","A complete guide to useEffect",[15,16,17,21,22,26,27,29,30,34],"p",{},[18,19,20],"code",{},"useEffect"," is where React meets the outside world. Rendering is supposed to be a\npure calculation of UI from state and props — but real apps also fetch data, set up\nsubscriptions, start timers, and read from the DOM. Those are ",[23,24,25],"strong",{},"side effects",", and\n",[18,28,20],{}," is the hook that runs them safely, ",[31,32,33],"em",{},"after"," render, and keeps them in sync\nwith your data. It's also the hook people misuse the most. This guide builds an\naccurate mental model and then walks through dependencies, cleanup, timing, and the\npatterns and pitfalls that come up again and again.",[10,36,38],{"id":37},"what-an-effect-actually-is","What an effect actually is",[15,40,41,42,45],{},"The single best framing comes from the React docs: an effect lets you\n",[23,43,44],{},"synchronize a component with an external system",". Don't think \"run this code on\nmount\u002Fupdate.\" Think \"keep this outside thing (the document title, a subscription, a\nnetwork request) matching my current state and props.\"",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-jsx shiki shiki-themes github-light github-dark","useEffect(() => {\n  document.title = `${count} unread`\n}, [count])\n","jsx","",[18,54,55,74,93],{"__ignoreMap":52},[56,57,60,63,67,71],"span",{"class":58,"line":59},"line",1,[56,61,20],{"class":62},"sScJk",[56,64,66],{"class":65},"sVt8B","(() ",[56,68,70],{"class":69},"szBVR","=>",[56,72,73],{"class":65}," {\n",[56,75,77,80,83,87,90],{"class":58,"line":76},2,[56,78,79],{"class":65},"  document.title ",[56,81,82],{"class":69},"=",[56,84,86],{"class":85},"sZZnC"," `${",[56,88,89],{"class":65},"count",[56,91,92],{"class":85},"} unread`\n",[56,94,96],{"class":58,"line":95},3,[56,97,98],{"class":65},"}, [count])\n",[15,100,101,102,104],{},"React runs the effect ",[23,103,33],{}," it has rendered and committed changes to the DOM and\nthe browser has painted. That ordering is deliberate: effects never block the visual\nupdate, so the user sees the new frame first.",[10,106,108],{"id":107},"the-dependency-array","The dependency array",[15,110,111,112,115,116,119],{},"The second argument controls ",[23,113,114],{},"when"," the effect re-runs. React compares each item\nto its value from the previous render using ",[18,117,118],{},"Object.is",":",[121,122,123,130,144],"ul",{},[124,125,126,129],"li",{},[18,127,128],{},"[]"," — run once after the first render.",[124,131,132,135,136,139,140,143],{},[18,133,134],{},"[a, b]"," — run after the first render and whenever ",[18,137,138],{},"a"," or ",[18,141,142],{},"b"," change.",[124,145,146,147,150],{},"omitted — run after ",[23,148,149],{},"every"," render.",[47,152,154],{"className":49,"code":153,"language":51,"meta":52,"style":52},"useEffect(() => {\n  const sub = source.subscribe(id)\n  return () => sub.unsubscribe()\n}, [id]) \u002F\u002F re-subscribe only when id changes\n",[18,155,156,166,187,206],{"__ignoreMap":52},[56,157,158,160,162,164],{"class":58,"line":59},[56,159,20],{"class":62},[56,161,66],{"class":65},[56,163,70],{"class":69},[56,165,73],{"class":65},[56,167,168,171,175,178,181,184],{"class":58,"line":76},[56,169,170],{"class":69},"  const",[56,172,174],{"class":173},"sj4cs"," sub",[56,176,177],{"class":69}," =",[56,179,180],{"class":65}," source.",[56,182,183],{"class":62},"subscribe",[56,185,186],{"class":65},"(id)\n",[56,188,189,192,195,197,200,203],{"class":58,"line":95},[56,190,191],{"class":69},"  return",[56,193,194],{"class":65}," () ",[56,196,70],{"class":69},[56,198,199],{"class":65}," sub.",[56,201,202],{"class":62},"unsubscribe",[56,204,205],{"class":65},"()\n",[56,207,209,212],{"class":58,"line":208},4,[56,210,211],{"class":65},"}, [id]) ",[56,213,215],{"class":214},"sJ8bj","\u002F\u002F re-subscribe only when id changes\n",[15,217,218,219,222,223,226],{},"The non-negotiable rule, enforced by the ",[18,220,221],{},"react-hooks\u002Fexhaustive-deps"," lint: ",[23,224,225],{},"every\nreactive value the effect reads must appear in the array",". Leaving one out to \"run\nless often\" is the number-one cause of stale-data bugs.",[228,229,231],"h3",{"id":230},"why-object-and-function-dependencies-misbehave","Why object and function dependencies misbehave",[15,233,234,235,238],{},"Dependencies are compared by reference. An object, array, or function created during\nrender is a ",[31,236,237],{},"new reference every render",", so the effect thinks it changed and re-runs\nendlessly.",[47,240,242],{"className":49,"code":241,"language":51,"meta":52,"style":52},"\u002F\u002F options is new each render -> effect runs every render\nconst options = { id }\nuseEffect(() => subscribe(options), [options])\n",[18,243,244,249,262],{"__ignoreMap":52},[56,245,246],{"class":58,"line":59},[56,247,248],{"class":214},"\u002F\u002F options is new each render -> effect runs every render\n",[56,250,251,254,257,259],{"class":58,"line":76},[56,252,253],{"class":69},"const",[56,255,256],{"class":173}," options",[56,258,177],{"class":69},[56,260,261],{"class":65}," { id }\n",[56,263,264,266,268,270,273],{"class":58,"line":95},[56,265,20],{"class":62},[56,267,66],{"class":65},[56,269,70],{"class":69},[56,271,272],{"class":62}," subscribe",[56,274,275],{"class":65},"(options), [options])\n",[15,277,278,279,282,283,286,287,119],{},"Fix it by depending on the primitive inside (",[18,280,281],{},"[id]","), building the object inside the\neffect, or stabilizing it with ",[18,284,285],{},"useMemo","\u002F",[18,288,289],{},"useCallback",[47,291,293],{"className":49,"code":292,"language":51,"meta":52,"style":52},"const load = useCallback(() => fetchData(id), [id])\nuseEffect(() => { load() }, [load]) \u002F\u002F stable identity, re-runs only when id changes\n",[18,294,295,317],{"__ignoreMap":52},[56,296,297,299,302,304,307,309,311,314],{"class":58,"line":59},[56,298,253],{"class":69},[56,300,301],{"class":173}," load",[56,303,177],{"class":69},[56,305,306],{"class":62}," useCallback",[56,308,66],{"class":65},[56,310,70],{"class":69},[56,312,313],{"class":62}," fetchData",[56,315,316],{"class":65},"(id), [id])\n",[56,318,319,321,323,325,328,331,334],{"class":58,"line":76},[56,320,20],{"class":62},[56,322,66],{"class":65},[56,324,70],{"class":69},[56,326,327],{"class":65}," { ",[56,329,330],{"class":62},"load",[56,332,333],{"class":65},"() }, [load]) ",[56,335,336],{"class":214},"\u002F\u002F stable identity, re-runs only when id changes\n",[15,338,339,340,343],{},"Prefer depending on ",[23,341,342],{},"specific primitive fields"," over whole objects — it's both\nsafer and more precise.",[10,345,347],{"id":346},"cleanup-functions","Cleanup functions",[15,349,350,351,354,355,358],{},"If an effect sets something up, it should tear it down. Return a function and React\nruns it ",[23,352,353],{},"before the next effect run"," and ",[23,356,357],{},"on unmount",".",[47,360,362],{"className":49,"code":361,"language":51,"meta":52,"style":52},"useEffect(() => {\n  const id = setInterval(tick, 1000)\n  return () => clearInterval(id)\n}, [])\n",[18,363,364,374,395,408],{"__ignoreMap":52},[56,365,366,368,370,372],{"class":58,"line":59},[56,367,20],{"class":62},[56,369,66],{"class":65},[56,371,70],{"class":69},[56,373,73],{"class":65},[56,375,376,378,381,383,386,389,392],{"class":58,"line":76},[56,377,170],{"class":69},[56,379,380],{"class":173}," id",[56,382,177],{"class":69},[56,384,385],{"class":62}," setInterval",[56,387,388],{"class":65},"(tick, ",[56,390,391],{"class":173},"1000",[56,393,394],{"class":65},")\n",[56,396,397,399,401,403,406],{"class":58,"line":95},[56,398,191],{"class":69},[56,400,194],{"class":65},[56,402,70],{"class":69},[56,404,405],{"class":62}," clearInterval",[56,407,186],{"class":65},[56,409,410],{"class":58,"line":208},[56,411,412],{"class":65},"}, [])\n",[15,414,415,416,419,420,423,424,426,427,430,431,434,435,438,439,441,442,444],{},"The ordering on a dependency change is: run the ",[31,417,418],{},"previous"," cleanup, then run the ",[31,421,422],{},"new","\neffect. With ",[18,425,281],{},", changing ",[18,428,429],{},"id"," from ",[18,432,433],{},"1"," to ",[18,436,437],{},"2"," unsubscribes from ",[18,440,433],{}," first, then\nsubscribes to ",[18,443,437],{}," — no leaks, no duplicates. Forgetting cleanup is how you end up with\nN intervals firing after N renders.",[15,446,447,448,450,451,454,455,358],{},"An empty array ",[18,449,128],{}," makes the effect run once and its cleanup run on unmount — the\nhooks equivalent of ",[18,452,453],{},"componentDidMount"," + ",[18,456,457],{},"componentWillUnmount",[10,459,461],{"id":460},"effect-timing-useeffect-vs-uselayouteffect","Effect timing: useEffect vs useLayoutEffect",[15,463,464,465,119],{},"Both run after render, but at different moments relative to ",[23,466,467],{},"paint",[121,469,470,479],{},[124,471,472,474,475,478],{},[18,473,20],{}," runs ",[23,476,477],{},"asynchronously, after the browser paints",". Right for almost\neverything — data, subscriptions, logging.",[124,480,481,474,484,487],{},[18,482,483],{},"useLayoutEffect",[23,485,486],{},"synchronously after DOM mutations but before paint",", so\nyou can measure or adjust layout without a visible flicker.",[47,489,491],{"className":49,"code":490,"language":51,"meta":52,"style":52},"useLayoutEffect(() => {\n  const { height } = ref.current.getBoundingClientRect()\n  setTooltipTop(height) \u002F\u002F applied before the browser paints\n}, [])\n",[18,492,493,503,525,536],{"__ignoreMap":52},[56,494,495,497,499,501],{"class":58,"line":59},[56,496,483],{"class":62},[56,498,66],{"class":65},[56,500,70],{"class":69},[56,502,73],{"class":65},[56,504,505,507,509,512,515,517,520,523],{"class":58,"line":76},[56,506,170],{"class":69},[56,508,327],{"class":65},[56,510,511],{"class":173},"height",[56,513,514],{"class":65}," } ",[56,516,82],{"class":69},[56,518,519],{"class":65}," ref.current.",[56,521,522],{"class":62},"getBoundingClientRect",[56,524,205],{"class":65},[56,526,527,530,533],{"class":58,"line":95},[56,528,529],{"class":62},"  setTooltipTop",[56,531,532],{"class":65},"(height) ",[56,534,535],{"class":214},"\u002F\u002F applied before the browser paints\n",[56,537,538],{"class":58,"line":208},[56,539,412],{"class":65},[15,541,542,543,545,546,549,550,553],{},"Use ",[18,544,483],{}," only when you must read\u002Fwrite layout to avoid flicker; because\nit blocks painting, overusing it hurts performance. Neither runs during server-side\nrendering, which makes effects the right home for browser-only APIs (",[18,547,548],{},"window",",\n",[18,551,552],{},"localStorage",").",[10,555,557],{"id":556},"fetching-data-correctly","Fetching data correctly",[15,559,560,561,564],{},"Start the request in the effect, store the result in state, and guard against ",[23,562,563],{},"race\nconditions"," — if the input changes quickly, an older response can overwrite a newer\none.",[47,566,568],{"className":49,"code":567,"language":51,"meta":52,"style":52},"useEffect(() => {\n  const ctrl = new AbortController()\n  fetch(`\u002Fapi\u002F${id}`, { signal: ctrl.signal })\n    .then(r => r.json())\n    .then(setData)\n    .catch(e => { if (e.name !== 'AbortError') throw e })\n  return () => ctrl.abort() \u002F\u002F cancel the in-flight request on change\u002Funmount\n}, [id])\n",[18,569,570,580,597,616,642,652,690,711],{"__ignoreMap":52},[56,571,572,574,576,578],{"class":58,"line":59},[56,573,20],{"class":62},[56,575,66],{"class":65},[56,577,70],{"class":69},[56,579,73],{"class":65},[56,581,582,584,587,589,592,595],{"class":58,"line":76},[56,583,170],{"class":69},[56,585,586],{"class":173}," ctrl",[56,588,177],{"class":69},[56,590,591],{"class":69}," new",[56,593,594],{"class":62}," AbortController",[56,596,205],{"class":65},[56,598,599,602,605,608,610,613],{"class":58,"line":95},[56,600,601],{"class":62},"  fetch",[56,603,604],{"class":65},"(",[56,606,607],{"class":85},"`\u002Fapi\u002F${",[56,609,429],{"class":65},[56,611,612],{"class":85},"}`",[56,614,615],{"class":65},", { signal: ctrl.signal })\n",[56,617,618,621,624,626,630,633,636,639],{"class":58,"line":208},[56,619,620],{"class":65},"    .",[56,622,623],{"class":62},"then",[56,625,604],{"class":65},[56,627,629],{"class":628},"s4XuR","r",[56,631,632],{"class":69}," =>",[56,634,635],{"class":65}," r.",[56,637,638],{"class":62},"json",[56,640,641],{"class":65},"())\n",[56,643,645,647,649],{"class":58,"line":644},5,[56,646,620],{"class":65},[56,648,623],{"class":62},[56,650,651],{"class":65},"(setData)\n",[56,653,655,657,660,662,665,667,669,672,675,678,681,684,687],{"class":58,"line":654},6,[56,656,620],{"class":65},[56,658,659],{"class":62},"catch",[56,661,604],{"class":65},[56,663,664],{"class":628},"e",[56,666,632],{"class":69},[56,668,327],{"class":65},[56,670,671],{"class":69},"if",[56,673,674],{"class":65}," (e.name ",[56,676,677],{"class":69},"!==",[56,679,680],{"class":85}," 'AbortError'",[56,682,683],{"class":65},") ",[56,685,686],{"class":69},"throw",[56,688,689],{"class":65}," e })\n",[56,691,693,695,697,699,702,705,708],{"class":58,"line":692},7,[56,694,191],{"class":69},[56,696,194],{"class":65},[56,698,70],{"class":69},[56,700,701],{"class":65}," ctrl.",[56,703,704],{"class":62},"abort",[56,706,707],{"class":65},"() ",[56,709,710],{"class":214},"\u002F\u002F cancel the in-flight request on change\u002Funmount\n",[56,712,714],{"class":58,"line":713},8,[56,715,716],{"class":65},"}, [id])\n",[15,718,719],{},"The cleanup aborts the previous request before the next one starts, so stale data\ncan't clobber fresh data. The simpler \"ignore flag\" variant flips a boolean in cleanup\nand checks it before calling the setter. For real apps, the React docs recommend a\ndata library (React Query, SWR) that handles caching, deduping, retries, and race\nconditions for you — hand-rolled fetching reimplements all of that.",[10,721,723],{"id":722},"stale-closures","Stale closures",[15,725,726,727,730],{},"An effect closes over the props and state from the render it was created in. Read a\nvalue but omit it from the deps, and the effect keeps using the ",[31,728,729],{},"frozen"," value\nforever.",[47,732,734],{"className":49,"code":733,"language":51,"meta":52,"style":52},"\u002F\u002F count captured as 0 once -> logs 0, 0, 0...\nuseEffect(() => {\n  const id = setInterval(() => console.log(count), 1000)\n  return () => clearInterval(id)\n}, [])\n",[18,735,736,741,751,778,790],{"__ignoreMap":52},[56,737,738],{"class":58,"line":59},[56,739,740],{"class":214},"\u002F\u002F count captured as 0 once -> logs 0, 0, 0...\n",[56,742,743,745,747,749],{"class":58,"line":76},[56,744,20],{"class":62},[56,746,66],{"class":65},[56,748,70],{"class":69},[56,750,73],{"class":65},[56,752,753,755,757,759,761,763,765,768,771,774,776],{"class":58,"line":95},[56,754,170],{"class":69},[56,756,380],{"class":173},[56,758,177],{"class":69},[56,760,385],{"class":62},[56,762,66],{"class":65},[56,764,70],{"class":69},[56,766,767],{"class":65}," console.",[56,769,770],{"class":62},"log",[56,772,773],{"class":65},"(count), ",[56,775,391],{"class":173},[56,777,394],{"class":65},[56,779,780,782,784,786,788],{"class":58,"line":208},[56,781,191],{"class":69},[56,783,194],{"class":65},[56,785,70],{"class":69},[56,787,405],{"class":62},[56,789,186],{"class":65},[56,791,792],{"class":58,"line":644},[56,793,412],{"class":65},[15,795,796,797,800,801,804],{},"Fixes: include the dependency (which re-creates the interval), use a functional state\nupdater so you never read the stale value, or — for a deliberately long-lived effect —\nkeep the latest value in a ",[18,798,799],{},"useRef"," updated each render and read ",[18,802,803],{},"ref.current"," inside.",[47,806,808],{"className":49,"code":807,"language":51,"meta":52,"style":52},"const cbRef = useRef(onTick)\nuseEffect(() => { cbRef.current = onTick })       \u002F\u002F always current\nuseEffect(() => {\n  const id = setInterval(() => cbRef.current(), 1000)\n  return () => clearInterval(id)\n}, [])                                            \u002F\u002F set up once, always fresh\n",[18,809,810,825,844,854,881,893],{"__ignoreMap":52},[56,811,812,814,817,819,822],{"class":58,"line":59},[56,813,253],{"class":69},[56,815,816],{"class":173}," cbRef",[56,818,177],{"class":69},[56,820,821],{"class":62}," useRef",[56,823,824],{"class":65},"(onTick)\n",[56,826,827,829,831,833,836,838,841],{"class":58,"line":76},[56,828,20],{"class":62},[56,830,66],{"class":65},[56,832,70],{"class":69},[56,834,835],{"class":65}," { cbRef.current ",[56,837,82],{"class":69},[56,839,840],{"class":65}," onTick })       ",[56,842,843],{"class":214},"\u002F\u002F always current\n",[56,845,846,848,850,852],{"class":58,"line":95},[56,847,20],{"class":62},[56,849,66],{"class":65},[56,851,70],{"class":69},[56,853,73],{"class":65},[56,855,856,858,860,862,864,866,868,871,874,877,879],{"class":58,"line":208},[56,857,170],{"class":69},[56,859,380],{"class":173},[56,861,177],{"class":69},[56,863,385],{"class":62},[56,865,66],{"class":65},[56,867,70],{"class":69},[56,869,870],{"class":65}," cbRef.",[56,872,873],{"class":62},"current",[56,875,876],{"class":65},"(), ",[56,878,391],{"class":173},[56,880,394],{"class":65},[56,882,883,885,887,889,891],{"class":58,"line":644},[56,884,191],{"class":69},[56,886,194],{"class":65},[56,888,70],{"class":69},[56,890,405],{"class":62},[56,892,186],{"class":65},[56,894,895,898],{"class":58,"line":654},[56,896,897],{"class":65},"}, [])                                            ",[56,899,900],{"class":214},"\u002F\u002F set up once, always fresh\n",[10,902,904],{"id":903},"you-might-not-need-an-effect","You might not need an effect",[15,906,907],{},"The most common mistake is reaching for an effect when you don't need one. Two rules:",[15,909,910,913],{},[23,911,912],{},"Don't use an effect for things that happen in response to a user event."," Do those\nin the event handler.",[47,915,917],{"className":49,"code":916,"language":51,"meta":52,"style":52},"\u002F\u002F effect reacting to a click that already happened\nuseEffect(() => { if (submitted) postData() }, [submitted])\n\u002F\u002F just do it in the handler\nfunction onSubmit() { postData() }\n",[18,918,919,924,945,950],{"__ignoreMap":52},[56,920,921],{"class":58,"line":59},[56,922,923],{"class":214},"\u002F\u002F effect reacting to a click that already happened\n",[56,925,926,928,930,932,934,936,939,942],{"class":58,"line":76},[56,927,20],{"class":62},[56,929,66],{"class":65},[56,931,70],{"class":69},[56,933,327],{"class":65},[56,935,671],{"class":69},[56,937,938],{"class":65}," (submitted) ",[56,940,941],{"class":62},"postData",[56,943,944],{"class":65},"() }, [submitted])\n",[56,946,947],{"class":58,"line":95},[56,948,949],{"class":214},"\u002F\u002F just do it in the handler\n",[56,951,952,955,958,961,963],{"class":58,"line":208},[56,953,954],{"class":69},"function",[56,956,957],{"class":62}," onSubmit",[56,959,960],{"class":65},"() { ",[56,962,941],{"class":62},[56,964,965],{"class":65},"() }\n",[15,967,968,971],{},[23,969,970],{},"Don't use an effect to compute derived state."," Computing a value and syncing it via\nan effect causes an extra render and risks the copy going stale — derive it during\nrender, memoizing only if it's expensive.",[47,973,975],{"className":49,"code":974,"language":51,"meta":52,"style":52},"\u002F\u002F effect + extra state + extra render\nuseEffect(() => setFull(`${first} ${last}`), [first, last])\n\u002F\u002F derive in render\nconst full = `${first} ${last}`\n",[18,976,977,982,1012,1017],{"__ignoreMap":52},[56,978,979],{"class":58,"line":59},[56,980,981],{"class":214},"\u002F\u002F effect + extra state + extra render\n",[56,983,984,986,988,990,993,995,998,1001,1004,1007,1009],{"class":58,"line":76},[56,985,20],{"class":62},[56,987,66],{"class":65},[56,989,70],{"class":69},[56,991,992],{"class":62}," setFull",[56,994,604],{"class":65},[56,996,997],{"class":85},"`${",[56,999,1000],{"class":65},"first",[56,1002,1003],{"class":85},"} ${",[56,1005,1006],{"class":65},"last",[56,1008,612],{"class":85},[56,1010,1011],{"class":65},"), [first, last])\n",[56,1013,1014],{"class":58,"line":95},[56,1015,1016],{"class":214},"\u002F\u002F derive in render\n",[56,1018,1019,1021,1024,1026,1028,1030,1032,1034],{"class":58,"line":208},[56,1020,253],{"class":69},[56,1022,1023],{"class":173}," full",[56,1025,177],{"class":69},[56,1027,86],{"class":85},[56,1029,1000],{"class":65},[56,1031,1003],{"class":85},[56,1033,1006],{"class":65},[56,1035,1036],{"class":85},"}`\n",[15,1038,1039,1040,1043],{},"Effects are for ",[23,1041,1042],{},"synchronizing with external systems",", not for internal state\ncascades. Chains of effects that set state and trigger more effects are an\nanti-pattern; compute values together in render or do multi-step updates in one\nhandler.",[10,1045,1047],{"id":1046},"common-pitfalls","Common pitfalls",[121,1049,1050,1059,1065,1074],{},[124,1051,1052,1055,1056,358],{},[23,1053,1054],{},"Disabling the lint to \"run once\""," hides a stale-closure bug — fix the root cause\ninstead of silencing ",[18,1057,1058],{},"exhaustive-deps",[124,1060,1061,1064],{},[23,1062,1063],{},"Infinite loops"," come from setting state in an effect whose dependency changes as\na result, or from an unstable object\u002Farray\u002Ffunction dependency.",[124,1066,1067,1073],{},[23,1068,1069,1070],{},"The effect callback can't be ",[18,1071,1072],{},"async"," — an async function returns a Promise,\nwhich breaks cleanup. Define an inner async function and call it.",[124,1075,1076,1079,1080,1082],{},[23,1077,1078],{},"Strict Mode in development"," mounts -> unmounts -> remounts, so your ",[18,1081,128],{}," effect and\ncleanup fire twice. That's a deliberate check that cleanup is correct; it doesn't\nhappen in production.",[47,1084,1086],{"className":49,"code":1085,"language":51,"meta":52,"style":52},"\u002F\u002F async work without breaking cleanup\nuseEffect(() => {\n  let ignore = false\n  ;(async () => {\n    const data = await load(id)\n    if (!ignore) setData(data)\n  })()\n  return () => { ignore = true }\n}, [id])\n",[18,1087,1088,1093,1103,1116,1129,1146,1166,1171,1190],{"__ignoreMap":52},[56,1089,1090],{"class":58,"line":59},[56,1091,1092],{"class":214},"\u002F\u002F async work without breaking cleanup\n",[56,1094,1095,1097,1099,1101],{"class":58,"line":76},[56,1096,20],{"class":62},[56,1098,66],{"class":65},[56,1100,70],{"class":69},[56,1102,73],{"class":65},[56,1104,1105,1108,1111,1113],{"class":58,"line":95},[56,1106,1107],{"class":69},"  let",[56,1109,1110],{"class":65}," ignore ",[56,1112,82],{"class":69},[56,1114,1115],{"class":173}," false\n",[56,1117,1118,1121,1123,1125,1127],{"class":58,"line":208},[56,1119,1120],{"class":65},"  ;(",[56,1122,1072],{"class":69},[56,1124,194],{"class":65},[56,1126,70],{"class":69},[56,1128,73],{"class":65},[56,1130,1131,1134,1137,1139,1142,1144],{"class":58,"line":644},[56,1132,1133],{"class":69},"    const",[56,1135,1136],{"class":173}," data",[56,1138,177],{"class":69},[56,1140,1141],{"class":69}," await",[56,1143,301],{"class":62},[56,1145,186],{"class":65},[56,1147,1148,1151,1154,1157,1160,1163],{"class":58,"line":654},[56,1149,1150],{"class":69},"    if",[56,1152,1153],{"class":65}," (",[56,1155,1156],{"class":69},"!",[56,1158,1159],{"class":65},"ignore) ",[56,1161,1162],{"class":62},"setData",[56,1164,1165],{"class":65},"(data)\n",[56,1167,1168],{"class":58,"line":692},[56,1169,1170],{"class":65},"  })()\n",[56,1172,1173,1175,1177,1179,1182,1184,1187],{"class":58,"line":713},[56,1174,191],{"class":69},[56,1176,194],{"class":65},[56,1178,70],{"class":69},[56,1180,1181],{"class":65}," { ignore ",[56,1183,82],{"class":69},[56,1185,1186],{"class":173}," true",[56,1188,1189],{"class":65}," }\n",[56,1191,1193],{"class":58,"line":1192},9,[56,1194,716],{"class":65},[10,1196,1198],{"id":1197},"recap","Recap",[15,1200,1201,1203,1204,1207,1208,1211,1212,1214,1215,1218],{},[18,1202,20],{}," synchronizes your component with the outside world, running after paint\nand re-running based on its ",[23,1205,1206],{},"dependency array"," — which must list every reactive\nvalue the effect reads. Return a ",[23,1209,1210],{},"cleanup"," function to tear down subscriptions,\ntimers, and requests; React runs it before each re-run and on unmount. Reach for\n",[18,1213,483],{}," only when you must touch layout before paint. Guard data fetching\nagainst race conditions, avoid stale closures by listing deps or using refs, and —\nmost importantly — ",[23,1216,1217],{},"don't use an effect"," for event responses or derived state.\nInternalize those ideas and effects stop being the scary hook.",[1220,1221,1222],"style",{},"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 pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":52,"searchDepth":76,"depth":76,"links":1224},[1225,1226,1227,1230,1231,1232,1233,1234,1235,1236],{"id":12,"depth":76,"text":13},{"id":37,"depth":76,"text":38},{"id":107,"depth":76,"text":108,"children":1228},[1229],{"id":230,"depth":95,"text":231},{"id":346,"depth":76,"text":347},{"id":460,"depth":76,"text":461},{"id":556,"depth":76,"text":557},{"id":722,"depth":76,"text":723},{"id":903,"depth":76,"text":904},{"id":1046,"depth":76,"text":1047},{"id":1197,"depth":76,"text":1198},"React useEffect interview questions and answers — the dependency array, cleanup functions, effect timing and common mistakes.","medium","md","React","react",{},true,"\u002Fblog\u002Freact-useeffect-hook-complete-guide","\u002Freact\u002Fhooks\u002Fuseeffect",{"title":5,"description":1237},"blog\u002Freact-useeffect-hook-complete-guide","Hooks","hooks","2026-06-17","mCj3sC4RMrxKek6BN47bz5MBNgp59a0oWofuCgTiK4E",1781808673080]