[{"data":1,"prerenderedAt":7683},["ShallowReactive",2],{"page-\u002Freact\u002F17-fetching-data":3},{"id":4,"title":5,"body":6,"description":7676,"extension":7677,"meta":7678,"navigation":58,"path":7679,"seo":7680,"stem":7681,"__hash__":7682},"content\u002Freact\u002F17-fetching-data.md","17 — Fetching Data",{"type":7,"value":8,"toc":7643},"minimark",[9,13,18,483,488,725,729,942,950,1200,1208,1506,1510,1844,1848,2341,2345,3046,3050,3559,3563,3903,3907,4308,4312,4989,4993,5490,5494,5629,5638,5880,5884,6306,6310,6458,6462,6637,6641,6807,6811,7007,7011,7014,7190,7455,7459,7639],[10,11,5],"h1",{"id":12},"_17-fetching-data",[14,15,17],"h2",{"id":16},"the-four-states-every-fetch-needs","The Four States Every Fetch Needs",[19,20,23],"code-wrapper",{"filename":21,"language":22},"four_states.js","javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F A production-grade fetch is never just \"loading or not\" — it needs at minimum:\n\u002F\u002F IDLE, LOADING, ERROR, SUCCESS, and SUCCESS often needs a further distinction\n\u002F\u002F for an empty result set (chapter 12).\n\nfunction UserProfile({ userId }) {\n  const [state, setState] = useState({ status: 'loading', data: null, error: null })\n\n  useEffect(() => {\n    setState({ status: 'loading', data: null, error: null })\n\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n      .then(res => {\n        if (!res.ok) throw new Error(`HTTP ${res.status}`)\n        \u002F\u002F fetch() does NOT reject for HTTP error statuses (404, 500) — only for\n        \u002F\u002F genuine network failures. A 404 is a successful round trip carrying an\n        \u002F\u002F error code. Skipping res.ok is the #1 data-fetching bug: \"not found\"\n        \u002F\u002F silently treated as \"success\" with undefined data.\n        return res.json()\n      })\n      .then(data => setState({ status: 'success', data, error: null }))\n      .catch(error => setState({ status: 'error', data: null, error }))\n  }, [userId])\n\n  if (state.status === 'loading') return \u003CSpinner \u002F>\n  if (state.status === 'error') return \u003CErrorMessage error={state.error} \u002F>\n  if (state.data.length === 0) return \u003CEmptyState \u002F>\n  return \u003CProfileCard user={state.data} \u002F>\n}\n","",[30,31,32,41,47,53,60,82,131,136,151,171,176,196,215,256,262,268,274,280,295,301,331,360,366,371,401,430,458,477],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F A production-grade fetch is never just \"loading or not\" — it needs at minimum:\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F IDLE, LOADING, ERROR, SUCCESS, and SUCCESS often needs a further distinction\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"\u002F\u002F for an empty result set (chapter 12).\n",[33,54,56],{"class":35,"line":55},4,[33,57,59],{"emptyLinePlaceholder":58},true,"\n",[33,61,63,67,71,75,79],{"class":35,"line":62},5,[33,64,66],{"class":65},"svdQ7","function",[33,68,70],{"class":69},"sIsaT"," UserProfile",[33,72,74],{"class":73},"ssxIu","({ ",[33,76,78],{"class":77},"sCrzJ","userId",[33,80,81],{"class":73}," }) {\n",[33,83,85,88,91,95,98,101,104,107,110,113,117,120,123,126,128],{"class":35,"line":84},6,[33,86,87],{"class":65},"  const",[33,89,90],{"class":73}," [",[33,92,94],{"class":93},"snvgF","state",[33,96,97],{"class":73},", ",[33,99,100],{"class":93},"setState",[33,102,103],{"class":73},"] ",[33,105,106],{"class":65},"=",[33,108,109],{"class":69}," useState",[33,111,112],{"class":73},"({ status: ",[33,114,116],{"class":115},"sJ6F3","'loading'",[33,118,119],{"class":73},", data: ",[33,121,122],{"class":93},"null",[33,124,125],{"class":73},", error: ",[33,127,122],{"class":93},[33,129,130],{"class":73}," })\n",[33,132,134],{"class":35,"line":133},7,[33,135,59],{"emptyLinePlaceholder":58},[33,137,139,142,145,148],{"class":35,"line":138},8,[33,140,141],{"class":69},"  useEffect",[33,143,144],{"class":73},"(() ",[33,146,147],{"class":65},"=>",[33,149,150],{"class":73}," {\n",[33,152,154,157,159,161,163,165,167,169],{"class":35,"line":153},9,[33,155,156],{"class":69},"    setState",[33,158,112],{"class":73},[33,160,116],{"class":115},[33,162,119],{"class":73},[33,164,122],{"class":93},[33,166,125],{"class":73},[33,168,122],{"class":93},[33,170,130],{"class":73},[33,172,174],{"class":35,"line":173},10,[33,175,59],{"emptyLinePlaceholder":58},[33,177,179,182,185,188,190,193],{"class":35,"line":178},11,[33,180,181],{"class":69},"    fetch",[33,183,184],{"class":73},"(",[33,186,187],{"class":115},"`\u002Fapi\u002Fusers\u002F${",[33,189,78],{"class":73},[33,191,192],{"class":115},"}`",[33,194,195],{"class":73},")\n",[33,197,199,202,205,207,210,213],{"class":35,"line":198},12,[33,200,201],{"class":73},"      .",[33,203,204],{"class":69},"then",[33,206,184],{"class":73},[33,208,209],{"class":77},"res",[33,211,212],{"class":65}," =>",[33,214,150],{"class":73},[33,216,218,221,224,227,230,233,236,239,241,244,246,249,252,254],{"class":35,"line":217},13,[33,219,220],{"class":65},"        if",[33,222,223],{"class":73}," (",[33,225,226],{"class":65},"!",[33,228,229],{"class":73},"res.ok) ",[33,231,232],{"class":65},"throw",[33,234,235],{"class":65}," new",[33,237,238],{"class":69}," Error",[33,240,184],{"class":73},[33,242,243],{"class":115},"`HTTP ${",[33,245,209],{"class":73},[33,247,248],{"class":115},".",[33,250,251],{"class":73},"status",[33,253,192],{"class":115},[33,255,195],{"class":73},[33,257,259],{"class":35,"line":258},14,[33,260,261],{"class":39},"        \u002F\u002F fetch() does NOT reject for HTTP error statuses (404, 500) — only for\n",[33,263,265],{"class":35,"line":264},15,[33,266,267],{"class":39},"        \u002F\u002F genuine network failures. A 404 is a successful round trip carrying an\n",[33,269,271],{"class":35,"line":270},16,[33,272,273],{"class":39},"        \u002F\u002F error code. Skipping res.ok is the #1 data-fetching bug: \"not found\"\n",[33,275,277],{"class":35,"line":276},17,[33,278,279],{"class":39},"        \u002F\u002F silently treated as \"success\" with undefined data.\n",[33,281,283,286,289,292],{"class":35,"line":282},18,[33,284,285],{"class":65},"        return",[33,287,288],{"class":73}," res.",[33,290,291],{"class":69},"json",[33,293,294],{"class":73},"()\n",[33,296,298],{"class":35,"line":297},19,[33,299,300],{"class":73},"      })\n",[33,302,304,306,308,310,313,315,318,320,323,326,328],{"class":35,"line":303},20,[33,305,201],{"class":73},[33,307,204],{"class":69},[33,309,184],{"class":73},[33,311,312],{"class":77},"data",[33,314,212],{"class":65},[33,316,317],{"class":69}," setState",[33,319,112],{"class":73},[33,321,322],{"class":115},"'success'",[33,324,325],{"class":73},", data, error: ",[33,327,122],{"class":93},[33,329,330],{"class":73}," }))\n",[33,332,334,336,339,341,344,346,348,350,353,355,357],{"class":35,"line":333},21,[33,335,201],{"class":73},[33,337,338],{"class":69},"catch",[33,340,184],{"class":73},[33,342,343],{"class":77},"error",[33,345,212],{"class":65},[33,347,317],{"class":69},[33,349,112],{"class":73},[33,351,352],{"class":115},"'error'",[33,354,119],{"class":73},[33,356,122],{"class":93},[33,358,359],{"class":73},", error }))\n",[33,361,363],{"class":35,"line":362},22,[33,364,365],{"class":73},"  }, [userId])\n",[33,367,369],{"class":35,"line":368},23,[33,370,59],{"emptyLinePlaceholder":58},[33,372,374,377,380,383,386,389,392,395,398],{"class":35,"line":373},24,[33,375,376],{"class":65},"  if",[33,378,379],{"class":73}," (state.status ",[33,381,382],{"class":65},"===",[33,384,385],{"class":115}," 'loading'",[33,387,388],{"class":73},") ",[33,390,391],{"class":65},"return",[33,393,394],{"class":73}," \u003C",[33,396,397],{"class":93},"Spinner",[33,399,400],{"class":73}," \u002F>\n",[33,402,404,406,408,410,413,415,417,419,422,425,427],{"class":35,"line":403},25,[33,405,376],{"class":65},[33,407,379],{"class":73},[33,409,382],{"class":65},[33,411,412],{"class":115}," 'error'",[33,414,388],{"class":73},[33,416,391],{"class":65},[33,418,394],{"class":73},[33,420,421],{"class":93},"ErrorMessage",[33,423,424],{"class":69}," error",[33,426,106],{"class":65},[33,428,429],{"class":73},"{state.error} \u002F>\n",[33,431,433,435,438,441,444,447,449,451,453,456],{"class":35,"line":432},26,[33,434,376],{"class":65},[33,436,437],{"class":73}," (state.data.",[33,439,440],{"class":93},"length",[33,442,443],{"class":65}," ===",[33,445,446],{"class":93}," 0",[33,448,388],{"class":73},[33,450,391],{"class":65},[33,452,394],{"class":73},[33,454,455],{"class":93},"EmptyState",[33,457,400],{"class":73},[33,459,461,464,466,469,472,474],{"class":35,"line":460},27,[33,462,463],{"class":65},"  return",[33,465,394],{"class":73},[33,467,468],{"class":93},"ProfileCard",[33,470,471],{"class":69}," user",[33,473,106],{"class":65},[33,475,476],{"class":73},"{state.data} \u002F>\n",[33,478,480],{"class":35,"line":479},28,[33,481,482],{"class":73},"}\n",[484,485,487],"h3",{"id":486},"anti-pattern-boolean-loading-flag","Anti-Pattern: Boolean Loading Flag",[19,489,491],{"filename":490,"language":22},"boolean_loading.js",[24,492,494],{"className":26,"code":493,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: a single boolean `isLoading` can't distinguish \"haven't started\n\u002F\u002F yet\" from \"started and failed\" from \"started and succeeded\" — and conflates\n\u002F\u002F error + empty into the same \"no data\" state, making UI branching ambiguous.\n\nfunction BadFetch({ userId }) {\n  const [data, setData] = useState(null)\n  const [isLoading, setLoading] = useState(true)\n\n  useEffect(() => {\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n      .then(res => res.json())\n      .then(data => { setData(data); setLoading(false) })\n      .catch(() => setLoading(false))  \u002F\u002F error swallowed — \"done\" but no data, no error UI\n  }, [userId])\n\n  if (isLoading) return \u003CSpinner \u002F>\n  return \u003CProfileCard user={data} \u002F>  \u002F\u002F if fetch failed, data is null → crash in ProfileCard\n}\n",[30,495,496,501,506,511,515,528,553,580,584,594,608,627,657,680,684,688,703,721],{"__ignoreMap":28},[33,497,498],{"class":35,"line":36},[33,499,500],{"class":39},"\u002F\u002F ANTI-PATTERN: a single boolean `isLoading` can't distinguish \"haven't started\n",[33,502,503],{"class":35,"line":43},[33,504,505],{"class":39},"\u002F\u002F yet\" from \"started and failed\" from \"started and succeeded\" — and conflates\n",[33,507,508],{"class":35,"line":49},[33,509,510],{"class":39},"\u002F\u002F error + empty into the same \"no data\" state, making UI branching ambiguous.\n",[33,512,513],{"class":35,"line":55},[33,514,59],{"emptyLinePlaceholder":58},[33,516,517,519,522,524,526],{"class":35,"line":62},[33,518,66],{"class":65},[33,520,521],{"class":69}," BadFetch",[33,523,74],{"class":73},[33,525,78],{"class":77},[33,527,81],{"class":73},[33,529,530,532,534,536,538,541,543,545,547,549,551],{"class":35,"line":84},[33,531,87],{"class":65},[33,533,90],{"class":73},[33,535,312],{"class":93},[33,537,97],{"class":73},[33,539,540],{"class":93},"setData",[33,542,103],{"class":73},[33,544,106],{"class":65},[33,546,109],{"class":69},[33,548,184],{"class":73},[33,550,122],{"class":93},[33,552,195],{"class":73},[33,554,555,557,559,562,564,567,569,571,573,575,578],{"class":35,"line":133},[33,556,87],{"class":65},[33,558,90],{"class":73},[33,560,561],{"class":93},"isLoading",[33,563,97],{"class":73},[33,565,566],{"class":93},"setLoading",[33,568,103],{"class":73},[33,570,106],{"class":65},[33,572,109],{"class":69},[33,574,184],{"class":73},[33,576,577],{"class":93},"true",[33,579,195],{"class":73},[33,581,582],{"class":35,"line":138},[33,583,59],{"emptyLinePlaceholder":58},[33,585,586,588,590,592],{"class":35,"line":153},[33,587,141],{"class":69},[33,589,144],{"class":73},[33,591,147],{"class":65},[33,593,150],{"class":73},[33,595,596,598,600,602,604,606],{"class":35,"line":173},[33,597,181],{"class":69},[33,599,184],{"class":73},[33,601,187],{"class":115},[33,603,78],{"class":73},[33,605,192],{"class":115},[33,607,195],{"class":73},[33,609,610,612,614,616,618,620,622,624],{"class":35,"line":178},[33,611,201],{"class":73},[33,613,204],{"class":69},[33,615,184],{"class":73},[33,617,209],{"class":77},[33,619,212],{"class":65},[33,621,288],{"class":73},[33,623,291],{"class":69},[33,625,626],{"class":73},"())\n",[33,628,629,631,633,635,637,639,642,644,647,649,651,654],{"class":35,"line":198},[33,630,201],{"class":73},[33,632,204],{"class":69},[33,634,184],{"class":73},[33,636,312],{"class":77},[33,638,212],{"class":65},[33,640,641],{"class":73}," { ",[33,643,540],{"class":69},[33,645,646],{"class":73},"(data); ",[33,648,566],{"class":69},[33,650,184],{"class":73},[33,652,653],{"class":93},"false",[33,655,656],{"class":73},") })\n",[33,658,659,661,663,665,667,670,672,674,677],{"class":35,"line":217},[33,660,201],{"class":73},[33,662,338],{"class":69},[33,664,144],{"class":73},[33,666,147],{"class":65},[33,668,669],{"class":69}," setLoading",[33,671,184],{"class":73},[33,673,653],{"class":93},[33,675,676],{"class":73},"))  ",[33,678,679],{"class":39},"\u002F\u002F error swallowed — \"done\" but no data, no error UI\n",[33,681,682],{"class":35,"line":258},[33,683,365],{"class":73},[33,685,686],{"class":35,"line":264},[33,687,59],{"emptyLinePlaceholder":58},[33,689,690,692,695,697,699,701],{"class":35,"line":270},[33,691,376],{"class":65},[33,693,694],{"class":73}," (isLoading) ",[33,696,391],{"class":65},[33,698,394],{"class":73},[33,700,397],{"class":93},[33,702,400],{"class":73},[33,704,705,707,709,711,713,715,718],{"class":35,"line":276},[33,706,463],{"class":65},[33,708,394],{"class":73},[33,710,468],{"class":93},[33,712,471],{"class":69},[33,714,106],{"class":65},[33,716,717],{"class":73},"{data} \u002F>  ",[33,719,720],{"class":39},"\u002F\u002F if fetch failed, data is null → crash in ProfileCard\n",[33,722,723],{"class":35,"line":282},[33,724,482],{"class":73},[14,726,728],{"id":727},"the-race-condition-fast-successive-requests","The Race Condition: Fast Successive Requests",[19,730,732],{"filename":731,"language":22},"race_condition.js",[24,733,735],{"className":26,"code":734,"language":22,"meta":28,"style":28},"\u002F\u002F If the triggering value (userId, query) changes again before the first request\n\u002F\u002F resolves, both requests are in flight simultaneously, and WHICHEVER RESOLVES\n\u002F\u002F LAST WINS — not whichever was REQUESTED last.\n\nfunction SearchResults({ query }) {\n  const [results, setResults] = useState([])\n\n  useEffect(() => {\n    \u002F\u002F BROKEN: no protection against out-of-order resolution.\n    \u002F\u002F User types \"react\" → fires requests for \"r\", \"re\", \"rea\", \"reac\", \"react\".\n    \u002F\u002F If \"r\" (returning thousands of results, genuinely slow) resolves AFTER\n    \u002F\u002F \"react\", the stale \"r\" results overwrite the correct \"react\" results —\n    \u002F\u002F with no error and no visible sign anything went wrong.\n    fetch(`\u002Fapi\u002Fsearch?q=${query}`)\n      .then(res => res.json())\n      .then(data => setResults(data.results))\n  }, [query])\n\n  return \u003Cul>{results.map(r => \u003Cli key={r.id}>{r.title}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n",[30,736,737,742,747,752,756,770,793,797,807,812,817,822,827,832,847,865,883,888,892,938],{"__ignoreMap":28},[33,738,739],{"class":35,"line":36},[33,740,741],{"class":39},"\u002F\u002F If the triggering value (userId, query) changes again before the first request\n",[33,743,744],{"class":35,"line":43},[33,745,746],{"class":39},"\u002F\u002F resolves, both requests are in flight simultaneously, and WHICHEVER RESOLVES\n",[33,748,749],{"class":35,"line":49},[33,750,751],{"class":39},"\u002F\u002F LAST WINS — not whichever was REQUESTED last.\n",[33,753,754],{"class":35,"line":55},[33,755,59],{"emptyLinePlaceholder":58},[33,757,758,760,763,765,768],{"class":35,"line":62},[33,759,66],{"class":65},[33,761,762],{"class":69}," SearchResults",[33,764,74],{"class":73},[33,766,767],{"class":77},"query",[33,769,81],{"class":73},[33,771,772,774,776,779,781,784,786,788,790],{"class":35,"line":84},[33,773,87],{"class":65},[33,775,90],{"class":73},[33,777,778],{"class":93},"results",[33,780,97],{"class":73},[33,782,783],{"class":93},"setResults",[33,785,103],{"class":73},[33,787,106],{"class":65},[33,789,109],{"class":69},[33,791,792],{"class":73},"([])\n",[33,794,795],{"class":35,"line":133},[33,796,59],{"emptyLinePlaceholder":58},[33,798,799,801,803,805],{"class":35,"line":138},[33,800,141],{"class":69},[33,802,144],{"class":73},[33,804,147],{"class":65},[33,806,150],{"class":73},[33,808,809],{"class":35,"line":153},[33,810,811],{"class":39},"    \u002F\u002F BROKEN: no protection against out-of-order resolution.\n",[33,813,814],{"class":35,"line":173},[33,815,816],{"class":39},"    \u002F\u002F User types \"react\" → fires requests for \"r\", \"re\", \"rea\", \"reac\", \"react\".\n",[33,818,819],{"class":35,"line":178},[33,820,821],{"class":39},"    \u002F\u002F If \"r\" (returning thousands of results, genuinely slow) resolves AFTER\n",[33,823,824],{"class":35,"line":198},[33,825,826],{"class":39},"    \u002F\u002F \"react\", the stale \"r\" results overwrite the correct \"react\" results —\n",[33,828,829],{"class":35,"line":217},[33,830,831],{"class":39},"    \u002F\u002F with no error and no visible sign anything went wrong.\n",[33,833,834,836,838,841,843,845],{"class":35,"line":258},[33,835,181],{"class":69},[33,837,184],{"class":73},[33,839,840],{"class":115},"`\u002Fapi\u002Fsearch?q=${",[33,842,767],{"class":73},[33,844,192],{"class":115},[33,846,195],{"class":73},[33,848,849,851,853,855,857,859,861,863],{"class":35,"line":264},[33,850,201],{"class":73},[33,852,204],{"class":69},[33,854,184],{"class":73},[33,856,209],{"class":77},[33,858,212],{"class":65},[33,860,288],{"class":73},[33,862,291],{"class":69},[33,864,626],{"class":73},[33,866,867,869,871,873,875,877,880],{"class":35,"line":270},[33,868,201],{"class":73},[33,870,204],{"class":69},[33,872,184],{"class":73},[33,874,312],{"class":77},[33,876,212],{"class":65},[33,878,879],{"class":69}," setResults",[33,881,882],{"class":73},"(data.results))\n",[33,884,885],{"class":35,"line":276},[33,886,887],{"class":73},"  }, [query])\n",[33,889,890],{"class":35,"line":282},[33,891,59],{"emptyLinePlaceholder":58},[33,893,894,896,898,902,905,908,910,913,915,917,920,923,925,928,930,933,935],{"class":35,"line":297},[33,895,463],{"class":65},[33,897,394],{"class":73},[33,899,901],{"class":900},"sk71V","ul",[33,903,904],{"class":73},">{results.",[33,906,907],{"class":69},"map",[33,909,184],{"class":73},[33,911,912],{"class":77},"r",[33,914,212],{"class":65},[33,916,394],{"class":73},[33,918,919],{"class":900},"li",[33,921,922],{"class":69}," key",[33,924,106],{"class":65},[33,926,927],{"class":73},"{r.id}>{r.title}\u003C\u002F",[33,929,919],{"class":900},[33,931,932],{"class":73},">)}\u003C\u002F",[33,934,901],{"class":900},[33,936,937],{"class":73},">\n",[33,939,940],{"class":35,"line":303},[33,941,482],{"class":73},[484,943,945,946,949],{"id":944},"fix-1-the-cancelled-flag","Fix 1: The ",[30,947,948],{},"cancelled"," Flag",[19,951,953],{"filename":952,"language":22},"cancelled_flag.js",[24,954,956],{"className":26,"code":955,"language":22,"meta":28,"style":28},"\u002F\u002F Each effect run closes over its own independent `cancelled` variable. When\n\u002F\u002F `query` changes, React runs the PREVIOUS effect's cleanup first, flipping\n\u002F\u002F THAT run's `cancelled` to true before starting the new effect with a fresh\n\u002F\u002F `cancelled = false`. The stale request's .then() still fires, but its result\n\u002F\u002F is discarded because its closure's `cancelled` is true by then.\n\nfunction SearchResults({ query }) {\n  const [results, setResults] = useState([])\n\n  useEffect(() => {\n    let cancelled = false\n\n    fetch(`\u002Fapi\u002Fsearch?q=${query}`)\n      .then(res => res.json())\n      .then(data => {\n        if (!cancelled) setResults(data.results)\n        \u002F\u002F If a newer effect run already set cancelled = true for this closure,\n        \u002F\u002F this stale response is discarded instead of overwriting fresher data.\n      })\n\n    return () => { cancelled = true }\n  }, [query])\n\n  return \u003Cul>{results.map(r => \u003Cli key={r.id}>{r.title}\u003C\u002Fli>)}\u003C\u002Ful>}\n}\n",[30,957,958,963,968,973,978,983,987,999,1019,1023,1033,1046,1050,1064,1082,1096,1112,1117,1122,1126,1130,1151,1155,1159,1196],{"__ignoreMap":28},[33,959,960],{"class":35,"line":36},[33,961,962],{"class":39},"\u002F\u002F Each effect run closes over its own independent `cancelled` variable. When\n",[33,964,965],{"class":35,"line":43},[33,966,967],{"class":39},"\u002F\u002F `query` changes, React runs the PREVIOUS effect's cleanup first, flipping\n",[33,969,970],{"class":35,"line":49},[33,971,972],{"class":39},"\u002F\u002F THAT run's `cancelled` to true before starting the new effect with a fresh\n",[33,974,975],{"class":35,"line":55},[33,976,977],{"class":39},"\u002F\u002F `cancelled = false`. The stale request's .then() still fires, but its result\n",[33,979,980],{"class":35,"line":62},[33,981,982],{"class":39},"\u002F\u002F is discarded because its closure's `cancelled` is true by then.\n",[33,984,985],{"class":35,"line":84},[33,986,59],{"emptyLinePlaceholder":58},[33,988,989,991,993,995,997],{"class":35,"line":133},[33,990,66],{"class":65},[33,992,762],{"class":69},[33,994,74],{"class":73},[33,996,767],{"class":77},[33,998,81],{"class":73},[33,1000,1001,1003,1005,1007,1009,1011,1013,1015,1017],{"class":35,"line":138},[33,1002,87],{"class":65},[33,1004,90],{"class":73},[33,1006,778],{"class":93},[33,1008,97],{"class":73},[33,1010,783],{"class":93},[33,1012,103],{"class":73},[33,1014,106],{"class":65},[33,1016,109],{"class":69},[33,1018,792],{"class":73},[33,1020,1021],{"class":35,"line":153},[33,1022,59],{"emptyLinePlaceholder":58},[33,1024,1025,1027,1029,1031],{"class":35,"line":173},[33,1026,141],{"class":69},[33,1028,144],{"class":73},[33,1030,147],{"class":65},[33,1032,150],{"class":73},[33,1034,1035,1038,1041,1043],{"class":35,"line":178},[33,1036,1037],{"class":65},"    let",[33,1039,1040],{"class":73}," cancelled ",[33,1042,106],{"class":65},[33,1044,1045],{"class":93}," false\n",[33,1047,1048],{"class":35,"line":198},[33,1049,59],{"emptyLinePlaceholder":58},[33,1051,1052,1054,1056,1058,1060,1062],{"class":35,"line":217},[33,1053,181],{"class":69},[33,1055,184],{"class":73},[33,1057,840],{"class":115},[33,1059,767],{"class":73},[33,1061,192],{"class":115},[33,1063,195],{"class":73},[33,1065,1066,1068,1070,1072,1074,1076,1078,1080],{"class":35,"line":258},[33,1067,201],{"class":73},[33,1069,204],{"class":69},[33,1071,184],{"class":73},[33,1073,209],{"class":77},[33,1075,212],{"class":65},[33,1077,288],{"class":73},[33,1079,291],{"class":69},[33,1081,626],{"class":73},[33,1083,1084,1086,1088,1090,1092,1094],{"class":35,"line":264},[33,1085,201],{"class":73},[33,1087,204],{"class":69},[33,1089,184],{"class":73},[33,1091,312],{"class":77},[33,1093,212],{"class":65},[33,1095,150],{"class":73},[33,1097,1098,1100,1102,1104,1107,1109],{"class":35,"line":270},[33,1099,220],{"class":65},[33,1101,223],{"class":73},[33,1103,226],{"class":65},[33,1105,1106],{"class":73},"cancelled) ",[33,1108,783],{"class":69},[33,1110,1111],{"class":73},"(data.results)\n",[33,1113,1114],{"class":35,"line":276},[33,1115,1116],{"class":39},"        \u002F\u002F If a newer effect run already set cancelled = true for this closure,\n",[33,1118,1119],{"class":35,"line":282},[33,1120,1121],{"class":39},"        \u002F\u002F this stale response is discarded instead of overwriting fresher data.\n",[33,1123,1124],{"class":35,"line":297},[33,1125,300],{"class":73},[33,1127,1128],{"class":35,"line":303},[33,1129,59],{"emptyLinePlaceholder":58},[33,1131,1132,1135,1138,1140,1143,1145,1148],{"class":35,"line":333},[33,1133,1134],{"class":65},"    return",[33,1136,1137],{"class":73}," () ",[33,1139,147],{"class":65},[33,1141,1142],{"class":73}," { cancelled ",[33,1144,106],{"class":65},[33,1146,1147],{"class":93}," true",[33,1149,1150],{"class":73}," }\n",[33,1152,1153],{"class":35,"line":362},[33,1154,887],{"class":73},[33,1156,1157],{"class":35,"line":368},[33,1158,59],{"emptyLinePlaceholder":58},[33,1160,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193],{"class":35,"line":373},[33,1162,463],{"class":65},[33,1164,394],{"class":73},[33,1166,901],{"class":900},[33,1168,904],{"class":73},[33,1170,907],{"class":69},[33,1172,184],{"class":73},[33,1174,912],{"class":77},[33,1176,212],{"class":65},[33,1178,394],{"class":73},[33,1180,919],{"class":900},[33,1182,922],{"class":69},[33,1184,106],{"class":65},[33,1186,927],{"class":73},[33,1188,919],{"class":900},[33,1190,932],{"class":73},[33,1192,901],{"class":900},[33,1194,1195],{"class":73},">}\n",[33,1197,1198],{"class":35,"line":403},[33,1199,482],{"class":73},[484,1201,1203,1204,1207],{"id":1202},"fix-2-abortcontroller-actually-cancels-the-request","Fix 2: ",[30,1205,1206],{},"AbortController"," (Actually Cancels the Request)",[19,1209,1211],{"filename":1210,"language":22},"abort_controller.js",[24,1212,1214],{"className":26,"code":1213,"language":22,"meta":28,"style":28},"\u002F\u002F The cancelled-flag prevents a stale response from being APPLIED, but the\n\u002F\u002F underlying network request still completes, wasting bandwidth and server\n\u002F\u002F resources. AbortController actually CANCELS the request itself.\n\nfunction SearchResults({ query }) {\n  const [results, setResults] = useState([])\n\n  useEffect(() => {\n    const controller = new AbortController()\n\n    fetch(`\u002Fapi\u002Fsearch?q=${query}`, { signal: controller.signal })\n      .then(res => res.json())\n      .then(data => setResults(data.results))\n      .catch(err => {\n        if (err.name !== 'AbortError') throw err\n        \u002F\u002F AbortError is EXPECTED — it fires every time a newer request supersedes\n        \u002F\u002F this one. Rethrowing anything else preserves visibility into genuine failures.\n        \u002F\u002F Treating every abort as a real error would show a misleading error message\n        \u002F\u002F every time the user types another character — not actually a failure.\n      })\n\n    return () => controller.abort()\n  }, [query])\n\n  return \u003Cul>{results.map(r => \u003Cli key={r.id}>{r.title}\u003C\u002Fli>)}\u003C\u002Ful>}\n}\n\n\u002F\u002F When to use which:\n\u002F\u002F - cancelled flag: simpler, sufficient for UI-correctness with cheap requests\n\u002F\u002F - AbortController: additionally saves network\u002Fserver resources — worth it for\n\u002F\u002F   expensive requests or high-frequency scenarios like search-as-you-type\n",[30,1215,1216,1221,1226,1231,1235,1247,1267,1271,1281,1299,1303,1318,1336,1352,1367,1387,1392,1397,1402,1407,1411,1415,1431,1435,1439,1475,1479,1483,1488,1494,1500],{"__ignoreMap":28},[33,1217,1218],{"class":35,"line":36},[33,1219,1220],{"class":39},"\u002F\u002F The cancelled-flag prevents a stale response from being APPLIED, but the\n",[33,1222,1223],{"class":35,"line":43},[33,1224,1225],{"class":39},"\u002F\u002F underlying network request still completes, wasting bandwidth and server\n",[33,1227,1228],{"class":35,"line":49},[33,1229,1230],{"class":39},"\u002F\u002F resources. AbortController actually CANCELS the request itself.\n",[33,1232,1233],{"class":35,"line":55},[33,1234,59],{"emptyLinePlaceholder":58},[33,1236,1237,1239,1241,1243,1245],{"class":35,"line":62},[33,1238,66],{"class":65},[33,1240,762],{"class":69},[33,1242,74],{"class":73},[33,1244,767],{"class":77},[33,1246,81],{"class":73},[33,1248,1249,1251,1253,1255,1257,1259,1261,1263,1265],{"class":35,"line":84},[33,1250,87],{"class":65},[33,1252,90],{"class":73},[33,1254,778],{"class":93},[33,1256,97],{"class":73},[33,1258,783],{"class":93},[33,1260,103],{"class":73},[33,1262,106],{"class":65},[33,1264,109],{"class":69},[33,1266,792],{"class":73},[33,1268,1269],{"class":35,"line":133},[33,1270,59],{"emptyLinePlaceholder":58},[33,1272,1273,1275,1277,1279],{"class":35,"line":138},[33,1274,141],{"class":69},[33,1276,144],{"class":73},[33,1278,147],{"class":65},[33,1280,150],{"class":73},[33,1282,1283,1286,1289,1292,1294,1297],{"class":35,"line":153},[33,1284,1285],{"class":65},"    const",[33,1287,1288],{"class":93}," controller",[33,1290,1291],{"class":65}," =",[33,1293,235],{"class":65},[33,1295,1296],{"class":69}," AbortController",[33,1298,294],{"class":73},[33,1300,1301],{"class":35,"line":173},[33,1302,59],{"emptyLinePlaceholder":58},[33,1304,1305,1307,1309,1311,1313,1315],{"class":35,"line":178},[33,1306,181],{"class":69},[33,1308,184],{"class":73},[33,1310,840],{"class":115},[33,1312,767],{"class":73},[33,1314,192],{"class":115},[33,1316,1317],{"class":73},", { signal: controller.signal })\n",[33,1319,1320,1322,1324,1326,1328,1330,1332,1334],{"class":35,"line":198},[33,1321,201],{"class":73},[33,1323,204],{"class":69},[33,1325,184],{"class":73},[33,1327,209],{"class":77},[33,1329,212],{"class":65},[33,1331,288],{"class":73},[33,1333,291],{"class":69},[33,1335,626],{"class":73},[33,1337,1338,1340,1342,1344,1346,1348,1350],{"class":35,"line":217},[33,1339,201],{"class":73},[33,1341,204],{"class":69},[33,1343,184],{"class":73},[33,1345,312],{"class":77},[33,1347,212],{"class":65},[33,1349,879],{"class":69},[33,1351,882],{"class":73},[33,1353,1354,1356,1358,1360,1363,1365],{"class":35,"line":258},[33,1355,201],{"class":73},[33,1357,338],{"class":69},[33,1359,184],{"class":73},[33,1361,1362],{"class":77},"err",[33,1364,212],{"class":65},[33,1366,150],{"class":73},[33,1368,1369,1371,1374,1377,1380,1382,1384],{"class":35,"line":264},[33,1370,220],{"class":65},[33,1372,1373],{"class":73}," (err.name ",[33,1375,1376],{"class":65},"!==",[33,1378,1379],{"class":115}," 'AbortError'",[33,1381,388],{"class":73},[33,1383,232],{"class":65},[33,1385,1386],{"class":73}," err\n",[33,1388,1389],{"class":35,"line":270},[33,1390,1391],{"class":39},"        \u002F\u002F AbortError is EXPECTED — it fires every time a newer request supersedes\n",[33,1393,1394],{"class":35,"line":276},[33,1395,1396],{"class":39},"        \u002F\u002F this one. Rethrowing anything else preserves visibility into genuine failures.\n",[33,1398,1399],{"class":35,"line":282},[33,1400,1401],{"class":39},"        \u002F\u002F Treating every abort as a real error would show a misleading error message\n",[33,1403,1404],{"class":35,"line":297},[33,1405,1406],{"class":39},"        \u002F\u002F every time the user types another character — not actually a failure.\n",[33,1408,1409],{"class":35,"line":303},[33,1410,300],{"class":73},[33,1412,1413],{"class":35,"line":333},[33,1414,59],{"emptyLinePlaceholder":58},[33,1416,1417,1419,1421,1423,1426,1429],{"class":35,"line":362},[33,1418,1134],{"class":65},[33,1420,1137],{"class":73},[33,1422,147],{"class":65},[33,1424,1425],{"class":73}," controller.",[33,1427,1428],{"class":69},"abort",[33,1430,294],{"class":73},[33,1432,1433],{"class":35,"line":368},[33,1434,887],{"class":73},[33,1436,1437],{"class":35,"line":373},[33,1438,59],{"emptyLinePlaceholder":58},[33,1440,1441,1443,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1465,1467,1469,1471,1473],{"class":35,"line":403},[33,1442,463],{"class":65},[33,1444,394],{"class":73},[33,1446,901],{"class":900},[33,1448,904],{"class":73},[33,1450,907],{"class":69},[33,1452,184],{"class":73},[33,1454,912],{"class":77},[33,1456,212],{"class":65},[33,1458,394],{"class":73},[33,1460,919],{"class":900},[33,1462,922],{"class":69},[33,1464,106],{"class":65},[33,1466,927],{"class":73},[33,1468,919],{"class":900},[33,1470,932],{"class":73},[33,1472,901],{"class":900},[33,1474,1195],{"class":73},[33,1476,1477],{"class":35,"line":432},[33,1478,482],{"class":73},[33,1480,1481],{"class":35,"line":460},[33,1482,59],{"emptyLinePlaceholder":58},[33,1484,1485],{"class":35,"line":479},[33,1486,1487],{"class":39},"\u002F\u002F When to use which:\n",[33,1489,1491],{"class":35,"line":1490},29,[33,1492,1493],{"class":39},"\u002F\u002F - cancelled flag: simpler, sufficient for UI-correctness with cheap requests\n",[33,1495,1497],{"class":35,"line":1496},30,[33,1498,1499],{"class":39},"\u002F\u002F - AbortController: additionally saves network\u002Fserver resources — worth it for\n",[33,1501,1503],{"class":35,"line":1502},31,[33,1504,1505],{"class":39},"\u002F\u002F   expensive requests or high-frequency scenarios like search-as-you-type\n",[14,1507,1509],{"id":1508},"loading-states-that-dont-flicker","Loading States That Don't Flicker",[19,1511,1513],{"filename":1512,"language":22},"delayed_loading.js",[24,1514,1516],{"className":26,"code":1515,"language":22,"meta":28,"style":28},"\u002F\u002F A request resolving in 50ms and one in 5 seconds both pass through \"loading\" —\n\u002F\u002F but showing a spinner for 50ms produces a distracting flash, not useful feedback.\n\u002F\u002F A minimum-duration or delayed-appearance strategy smooths this out.\n\nfunction useDelayedLoading(isLoading, delayMs = 300) {\n  const [showLoading, setShowLoading] = useState(false)\n\n  useEffect(() => {\n    if (!isLoading) {\n      setShowLoading(false)\n      return\n    }\n    \u002F\u002F Only show the spinner if loading takes longer than delayMs — fast requests\n    \u002F\u002F resolve before the timeout fires, and the cleanup cancels it, so the UI\n    \u002F\u002F goes straight from \"nothing\" to \"content\" with no flash.\n    const id = setTimeout(() => setShowLoading(true), delayMs)\n    return () => clearTimeout(id)\n  }, [isLoading, delayMs])\n\n  return showLoading\n}\n\n\u002F\u002F Usage:\nfunction UserProfile({ userId }) {\n  const { status, data } = useFetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n  const showSpinner = useDelayedLoading(status === 'loading', 300)\n\n  if (showSpinner) return \u003CSpinner \u002F>\n  if (status === 'error') return \u003CErrorMessage \u002F>\n  return \u003CProfileCard user={data} \u002F>\n}\n",[30,1517,1518,1523,1528,1533,1537,1561,1587,1591,1601,1613,1624,1629,1634,1639,1644,1649,1675,1689,1694,1698,1705,1709,1713,1718,1730,1760,1785,1789,1804,1825,1840],{"__ignoreMap":28},[33,1519,1520],{"class":35,"line":36},[33,1521,1522],{"class":39},"\u002F\u002F A request resolving in 50ms and one in 5 seconds both pass through \"loading\" —\n",[33,1524,1525],{"class":35,"line":43},[33,1526,1527],{"class":39},"\u002F\u002F but showing a spinner for 50ms produces a distracting flash, not useful feedback.\n",[33,1529,1530],{"class":35,"line":49},[33,1531,1532],{"class":39},"\u002F\u002F A minimum-duration or delayed-appearance strategy smooths this out.\n",[33,1534,1535],{"class":35,"line":55},[33,1536,59],{"emptyLinePlaceholder":58},[33,1538,1539,1541,1544,1546,1548,1550,1553,1555,1558],{"class":35,"line":62},[33,1540,66],{"class":65},[33,1542,1543],{"class":69}," useDelayedLoading",[33,1545,184],{"class":73},[33,1547,561],{"class":77},[33,1549,97],{"class":73},[33,1551,1552],{"class":77},"delayMs",[33,1554,1291],{"class":65},[33,1556,1557],{"class":93}," 300",[33,1559,1560],{"class":73},") {\n",[33,1562,1563,1565,1567,1570,1572,1575,1577,1579,1581,1583,1585],{"class":35,"line":84},[33,1564,87],{"class":65},[33,1566,90],{"class":73},[33,1568,1569],{"class":93},"showLoading",[33,1571,97],{"class":73},[33,1573,1574],{"class":93},"setShowLoading",[33,1576,103],{"class":73},[33,1578,106],{"class":65},[33,1580,109],{"class":69},[33,1582,184],{"class":73},[33,1584,653],{"class":93},[33,1586,195],{"class":73},[33,1588,1589],{"class":35,"line":133},[33,1590,59],{"emptyLinePlaceholder":58},[33,1592,1593,1595,1597,1599],{"class":35,"line":138},[33,1594,141],{"class":69},[33,1596,144],{"class":73},[33,1598,147],{"class":65},[33,1600,150],{"class":73},[33,1602,1603,1606,1608,1610],{"class":35,"line":153},[33,1604,1605],{"class":65},"    if",[33,1607,223],{"class":73},[33,1609,226],{"class":65},[33,1611,1612],{"class":73},"isLoading) {\n",[33,1614,1615,1618,1620,1622],{"class":35,"line":173},[33,1616,1617],{"class":69},"      setShowLoading",[33,1619,184],{"class":73},[33,1621,653],{"class":93},[33,1623,195],{"class":73},[33,1625,1626],{"class":35,"line":178},[33,1627,1628],{"class":65},"      return\n",[33,1630,1631],{"class":35,"line":198},[33,1632,1633],{"class":73},"    }\n",[33,1635,1636],{"class":35,"line":217},[33,1637,1638],{"class":39},"    \u002F\u002F Only show the spinner if loading takes longer than delayMs — fast requests\n",[33,1640,1641],{"class":35,"line":258},[33,1642,1643],{"class":39},"    \u002F\u002F resolve before the timeout fires, and the cleanup cancels it, so the UI\n",[33,1645,1646],{"class":35,"line":264},[33,1647,1648],{"class":39},"    \u002F\u002F goes straight from \"nothing\" to \"content\" with no flash.\n",[33,1650,1651,1653,1656,1658,1661,1663,1665,1668,1670,1672],{"class":35,"line":270},[33,1652,1285],{"class":65},[33,1654,1655],{"class":93}," id",[33,1657,1291],{"class":65},[33,1659,1660],{"class":69}," setTimeout",[33,1662,144],{"class":73},[33,1664,147],{"class":65},[33,1666,1667],{"class":69}," setShowLoading",[33,1669,184],{"class":73},[33,1671,577],{"class":93},[33,1673,1674],{"class":73},"), delayMs)\n",[33,1676,1677,1679,1681,1683,1686],{"class":35,"line":276},[33,1678,1134],{"class":65},[33,1680,1137],{"class":73},[33,1682,147],{"class":65},[33,1684,1685],{"class":69}," clearTimeout",[33,1687,1688],{"class":73},"(id)\n",[33,1690,1691],{"class":35,"line":282},[33,1692,1693],{"class":73},"  }, [isLoading, delayMs])\n",[33,1695,1696],{"class":35,"line":297},[33,1697,59],{"emptyLinePlaceholder":58},[33,1699,1700,1702],{"class":35,"line":303},[33,1701,463],{"class":65},[33,1703,1704],{"class":73}," showLoading\n",[33,1706,1707],{"class":35,"line":333},[33,1708,482],{"class":73},[33,1710,1711],{"class":35,"line":362},[33,1712,59],{"emptyLinePlaceholder":58},[33,1714,1715],{"class":35,"line":368},[33,1716,1717],{"class":39},"\u002F\u002F Usage:\n",[33,1719,1720,1722,1724,1726,1728],{"class":35,"line":373},[33,1721,66],{"class":65},[33,1723,70],{"class":69},[33,1725,74],{"class":73},[33,1727,78],{"class":77},[33,1729,81],{"class":73},[33,1731,1732,1734,1736,1738,1740,1742,1745,1747,1750,1752,1754,1756,1758],{"class":35,"line":403},[33,1733,87],{"class":65},[33,1735,641],{"class":73},[33,1737,251],{"class":93},[33,1739,97],{"class":73},[33,1741,312],{"class":93},[33,1743,1744],{"class":73}," } ",[33,1746,106],{"class":65},[33,1748,1749],{"class":69}," useFetch",[33,1751,184],{"class":73},[33,1753,187],{"class":115},[33,1755,78],{"class":73},[33,1757,192],{"class":115},[33,1759,195],{"class":73},[33,1761,1762,1764,1767,1769,1771,1774,1776,1778,1780,1783],{"class":35,"line":432},[33,1763,87],{"class":65},[33,1765,1766],{"class":93}," showSpinner",[33,1768,1291],{"class":65},[33,1770,1543],{"class":69},[33,1772,1773],{"class":73},"(status ",[33,1775,382],{"class":65},[33,1777,385],{"class":115},[33,1779,97],{"class":73},[33,1781,1782],{"class":93},"300",[33,1784,195],{"class":73},[33,1786,1787],{"class":35,"line":460},[33,1788,59],{"emptyLinePlaceholder":58},[33,1790,1791,1793,1796,1798,1800,1802],{"class":35,"line":479},[33,1792,376],{"class":65},[33,1794,1795],{"class":73}," (showSpinner) ",[33,1797,391],{"class":65},[33,1799,394],{"class":73},[33,1801,397],{"class":93},[33,1803,400],{"class":73},[33,1805,1806,1808,1811,1813,1815,1817,1819,1821,1823],{"class":35,"line":1490},[33,1807,376],{"class":65},[33,1809,1810],{"class":73}," (status ",[33,1812,382],{"class":65},[33,1814,412],{"class":115},[33,1816,388],{"class":73},[33,1818,391],{"class":65},[33,1820,394],{"class":73},[33,1822,421],{"class":93},[33,1824,400],{"class":73},[33,1826,1827,1829,1831,1833,1835,1837],{"class":35,"line":1496},[33,1828,463],{"class":65},[33,1830,394],{"class":73},[33,1832,468],{"class":93},[33,1834,471],{"class":69},[33,1836,106],{"class":65},[33,1838,1839],{"class":73},"{data} \u002F>\n",[33,1841,1842],{"class":35,"line":1502},[33,1843,482],{"class":73},[14,1845,1847],{"id":1846},"retrying-failed-requests-with-backoff","Retrying Failed Requests with Backoff",[19,1849,1851],{"filename":1850,"language":22},"retry_backoff.js",[24,1852,1854],{"className":26,"code":1853,"language":22,"meta":28,"style":28},"\u002F\u002F Real networks fail transiently — retry with backoff often succeeds where a\n\u002F\u002F single attempt wouldn't, and is better UX than an immediate permanent error\n\u002F\u002F state for a one-off blip.\n\nasync function fetchWithRetry(url, { retries = 3, backoffMs = 500 } = {}) {\n  for (let attempt = 0; attempt \u003C= retries; attempt++) {\n    try {\n      const res = await fetch(url)\n      if (!res.ok) throw new Error(`HTTP ${res.status}`)\n      return await res.json()\n    } catch (err) {\n      const isLastAttempt = attempt === retries\n      if (isLastAttempt) throw err\n      \u002F\u002F Exponential backoff: 500ms, 1000ms, 2000ms, 4000ms...\n      \u002F\u002F Retrying a genuinely down server immediately adds load precisely when\n      \u002F\u002F it's least able to handle it; backoff gives transient issues time to resolve.\n      await new Promise(resolve => setTimeout(resolve, backoffMs * 2 ** attempt))\n    }\n  }\n}\n\n\u002F\u002F With AbortController support:\nasync function fetchWithRetryAndAbort(url, signal, { retries = 3, backoffMs = 500 } = {}) {\n  for (let attempt = 0; attempt \u003C= retries; attempt++) {\n    try {\n      const res = await fetch(url, { signal })\n      if (!res.ok) throw new Error(`HTTP ${res.status}`)\n      return await res.json()\n    } catch (err) {\n      if (err.name === 'AbortError') throw err  \u002F\u002F don't retry aborts\n      if (attempt === retries) throw err\n      await new Promise(resolve => setTimeout(resolve, backoffMs * 2 ** attempt))\n    }\n  }\n}\n",[30,1855,1856,1861,1866,1871,1875,1919,1950,1957,1976,2007,2020,2030,2046,2057,2062,2067,2072,2106,2110,2115,2119,2123,2128,2168,2192,2198,2213,2243,2255,2263,2283,2299,2326,2331,2336],{"__ignoreMap":28},[33,1857,1858],{"class":35,"line":36},[33,1859,1860],{"class":39},"\u002F\u002F Real networks fail transiently — retry with backoff often succeeds where a\n",[33,1862,1863],{"class":35,"line":43},[33,1864,1865],{"class":39},"\u002F\u002F single attempt wouldn't, and is better UX than an immediate permanent error\n",[33,1867,1868],{"class":35,"line":49},[33,1869,1870],{"class":39},"\u002F\u002F state for a one-off blip.\n",[33,1872,1873],{"class":35,"line":55},[33,1874,59],{"emptyLinePlaceholder":58},[33,1876,1877,1880,1883,1886,1888,1891,1894,1897,1899,1902,1904,1907,1909,1912,1914,1916],{"class":35,"line":62},[33,1878,1879],{"class":65},"async",[33,1881,1882],{"class":65}," function",[33,1884,1885],{"class":69}," fetchWithRetry",[33,1887,184],{"class":73},[33,1889,1890],{"class":77},"url",[33,1892,1893],{"class":73},", { ",[33,1895,1896],{"class":77},"retries",[33,1898,1291],{"class":65},[33,1900,1901],{"class":93}," 3",[33,1903,97],{"class":73},[33,1905,1906],{"class":77},"backoffMs",[33,1908,1291],{"class":65},[33,1910,1911],{"class":93}," 500",[33,1913,1744],{"class":73},[33,1915,106],{"class":65},[33,1917,1918],{"class":73}," {}) {\n",[33,1920,1921,1924,1926,1929,1932,1934,1936,1939,1942,1945,1948],{"class":35,"line":84},[33,1922,1923],{"class":65},"  for",[33,1925,223],{"class":73},[33,1927,1928],{"class":65},"let",[33,1930,1931],{"class":73}," attempt ",[33,1933,106],{"class":65},[33,1935,446],{"class":93},[33,1937,1938],{"class":73},"; attempt ",[33,1940,1941],{"class":65},"\u003C=",[33,1943,1944],{"class":73}," retries; attempt",[33,1946,1947],{"class":65},"++",[33,1949,1560],{"class":73},[33,1951,1952,1955],{"class":35,"line":133},[33,1953,1954],{"class":65},"    try",[33,1956,150],{"class":73},[33,1958,1959,1962,1965,1967,1970,1973],{"class":35,"line":138},[33,1960,1961],{"class":65},"      const",[33,1963,1964],{"class":93}," res",[33,1966,1291],{"class":65},[33,1968,1969],{"class":65}," await",[33,1971,1972],{"class":69}," fetch",[33,1974,1975],{"class":73},"(url)\n",[33,1977,1978,1981,1983,1985,1987,1989,1991,1993,1995,1997,1999,2001,2003,2005],{"class":35,"line":153},[33,1979,1980],{"class":65},"      if",[33,1982,223],{"class":73},[33,1984,226],{"class":65},[33,1986,229],{"class":73},[33,1988,232],{"class":65},[33,1990,235],{"class":65},[33,1992,238],{"class":69},[33,1994,184],{"class":73},[33,1996,243],{"class":115},[33,1998,209],{"class":73},[33,2000,248],{"class":115},[33,2002,251],{"class":73},[33,2004,192],{"class":115},[33,2006,195],{"class":73},[33,2008,2009,2012,2014,2016,2018],{"class":35,"line":173},[33,2010,2011],{"class":65},"      return",[33,2013,1969],{"class":65},[33,2015,288],{"class":73},[33,2017,291],{"class":69},[33,2019,294],{"class":73},[33,2021,2022,2025,2027],{"class":35,"line":178},[33,2023,2024],{"class":73},"    } ",[33,2026,338],{"class":65},[33,2028,2029],{"class":73}," (err) {\n",[33,2031,2032,2034,2037,2039,2041,2043],{"class":35,"line":198},[33,2033,1961],{"class":65},[33,2035,2036],{"class":93}," isLastAttempt",[33,2038,1291],{"class":65},[33,2040,1931],{"class":73},[33,2042,382],{"class":65},[33,2044,2045],{"class":73}," retries\n",[33,2047,2048,2050,2053,2055],{"class":35,"line":217},[33,2049,1980],{"class":65},[33,2051,2052],{"class":73}," (isLastAttempt) ",[33,2054,232],{"class":65},[33,2056,1386],{"class":73},[33,2058,2059],{"class":35,"line":258},[33,2060,2061],{"class":39},"      \u002F\u002F Exponential backoff: 500ms, 1000ms, 2000ms, 4000ms...\n",[33,2063,2064],{"class":35,"line":264},[33,2065,2066],{"class":39},"      \u002F\u002F Retrying a genuinely down server immediately adds load precisely when\n",[33,2068,2069],{"class":35,"line":270},[33,2070,2071],{"class":39},"      \u002F\u002F it's least able to handle it; backoff gives transient issues time to resolve.\n",[33,2073,2074,2077,2079,2082,2084,2087,2089,2091,2094,2097,2100,2103],{"class":35,"line":276},[33,2075,2076],{"class":65},"      await",[33,2078,235],{"class":65},[33,2080,2081],{"class":93}," Promise",[33,2083,184],{"class":73},[33,2085,2086],{"class":77},"resolve",[33,2088,212],{"class":65},[33,2090,1660],{"class":69},[33,2092,2093],{"class":73},"(resolve, backoffMs ",[33,2095,2096],{"class":65},"*",[33,2098,2099],{"class":93}," 2",[33,2101,2102],{"class":65}," **",[33,2104,2105],{"class":73}," attempt))\n",[33,2107,2108],{"class":35,"line":282},[33,2109,1633],{"class":73},[33,2111,2112],{"class":35,"line":297},[33,2113,2114],{"class":73},"  }\n",[33,2116,2117],{"class":35,"line":303},[33,2118,482],{"class":73},[33,2120,2121],{"class":35,"line":333},[33,2122,59],{"emptyLinePlaceholder":58},[33,2124,2125],{"class":35,"line":362},[33,2126,2127],{"class":39},"\u002F\u002F With AbortController support:\n",[33,2129,2130,2132,2134,2137,2139,2141,2143,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166],{"class":35,"line":368},[33,2131,1879],{"class":65},[33,2133,1882],{"class":65},[33,2135,2136],{"class":69}," fetchWithRetryAndAbort",[33,2138,184],{"class":73},[33,2140,1890],{"class":77},[33,2142,97],{"class":73},[33,2144,2145],{"class":77},"signal",[33,2147,1893],{"class":73},[33,2149,1896],{"class":77},[33,2151,1291],{"class":65},[33,2153,1901],{"class":93},[33,2155,97],{"class":73},[33,2157,1906],{"class":77},[33,2159,1291],{"class":65},[33,2161,1911],{"class":93},[33,2163,1744],{"class":73},[33,2165,106],{"class":65},[33,2167,1918],{"class":73},[33,2169,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190],{"class":35,"line":373},[33,2171,1923],{"class":65},[33,2173,223],{"class":73},[33,2175,1928],{"class":65},[33,2177,1931],{"class":73},[33,2179,106],{"class":65},[33,2181,446],{"class":93},[33,2183,1938],{"class":73},[33,2185,1941],{"class":65},[33,2187,1944],{"class":73},[33,2189,1947],{"class":65},[33,2191,1560],{"class":73},[33,2193,2194,2196],{"class":35,"line":403},[33,2195,1954],{"class":65},[33,2197,150],{"class":73},[33,2199,2200,2202,2204,2206,2208,2210],{"class":35,"line":432},[33,2201,1961],{"class":65},[33,2203,1964],{"class":93},[33,2205,1291],{"class":65},[33,2207,1969],{"class":65},[33,2209,1972],{"class":69},[33,2211,2212],{"class":73},"(url, { signal })\n",[33,2214,2215,2217,2219,2221,2223,2225,2227,2229,2231,2233,2235,2237,2239,2241],{"class":35,"line":460},[33,2216,1980],{"class":65},[33,2218,223],{"class":73},[33,2220,226],{"class":65},[33,2222,229],{"class":73},[33,2224,232],{"class":65},[33,2226,235],{"class":65},[33,2228,238],{"class":69},[33,2230,184],{"class":73},[33,2232,243],{"class":115},[33,2234,209],{"class":73},[33,2236,248],{"class":115},[33,2238,251],{"class":73},[33,2240,192],{"class":115},[33,2242,195],{"class":73},[33,2244,2245,2247,2249,2251,2253],{"class":35,"line":479},[33,2246,2011],{"class":65},[33,2248,1969],{"class":65},[33,2250,288],{"class":73},[33,2252,291],{"class":69},[33,2254,294],{"class":73},[33,2256,2257,2259,2261],{"class":35,"line":1490},[33,2258,2024],{"class":73},[33,2260,338],{"class":65},[33,2262,2029],{"class":73},[33,2264,2265,2267,2269,2271,2273,2275,2277,2280],{"class":35,"line":1496},[33,2266,1980],{"class":65},[33,2268,1373],{"class":73},[33,2270,382],{"class":65},[33,2272,1379],{"class":115},[33,2274,388],{"class":73},[33,2276,232],{"class":65},[33,2278,2279],{"class":73}," err  ",[33,2281,2282],{"class":39},"\u002F\u002F don't retry aborts\n",[33,2284,2285,2287,2290,2292,2295,2297],{"class":35,"line":1502},[33,2286,1980],{"class":65},[33,2288,2289],{"class":73}," (attempt ",[33,2291,382],{"class":65},[33,2293,2294],{"class":73}," retries) ",[33,2296,232],{"class":65},[33,2298,1386],{"class":73},[33,2300,2302,2304,2306,2308,2310,2312,2314,2316,2318,2320,2322,2324],{"class":35,"line":2301},32,[33,2303,2076],{"class":65},[33,2305,235],{"class":65},[33,2307,2081],{"class":93},[33,2309,184],{"class":73},[33,2311,2086],{"class":77},[33,2313,212],{"class":65},[33,2315,1660],{"class":69},[33,2317,2093],{"class":73},[33,2319,2096],{"class":65},[33,2321,2099],{"class":93},[33,2323,2102],{"class":65},[33,2325,2105],{"class":73},[33,2327,2329],{"class":35,"line":2328},33,[33,2330,1633],{"class":73},[33,2332,2334],{"class":35,"line":2333},34,[33,2335,2114],{"class":73},[33,2337,2339],{"class":35,"line":2338},35,[33,2340,482],{"class":73},[14,2342,2344],{"id":2343},"waterfalls-vs-parallel-fetching","Waterfalls vs Parallel Fetching",[19,2346,2348],{"filename":2347,"language":22},"waterfalls.js",[24,2349,2351],{"className":26,"code":2350,"language":22,"meta":28,"style":28},"\u002F\u002F WATERFALL: sequential fetches — each waits for the previous to complete.\n\u002F\u002F Total time = sum of all request times. Easy to write, slow at scale.\n\nfunction UserProfile({ userId }) {\n  const [user, setUser] = useState(null)\n  const [posts, setPosts] = useState(null)\n  const [friends, setFriends] = useState(null)\n\n  useEffect(() => {\n    \u002F\u002F Fetch user → wait → fetch posts → wait → fetch friends → wait\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(r => r.json()).then(u => {\n      setUser(u)\n      fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Fposts`).then(r => r.json()).then(p => {\n        setPosts(p)\n        fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Ffriends`).then(r => r.json()).then(setFriends)\n      })\n    })\n  }, [userId])\n  \u002F\u002F If each request takes 200ms, total = 600ms. Three independent requests\n  \u002F\u002F that could run in parallel take 3x as long as necessary.\n}\n\n\u002F\u002F FIX: parallel fetching with Promise.all — total time = slowest request.\nfunction UserProfileParallel({ userId }) {\n  const [state, setState] = useState({ status: 'loading', data: null, error: null })\n\n  useEffect(() => {\n    const controller = new AbortController()\n    setState({ status: 'loading', data: null, error: null })\n\n    Promise.all([\n      fetch(`\u002Fapi\u002Fusers\u002F${userId}`, { signal: controller.signal }).then(r => r.json()),\n      fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Fposts`, { signal: controller.signal }).then(r => r.json()),\n      fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Ffriends`, { signal: controller.signal }).then(r => r.json()),\n    ])\n      .then(([user, posts, friends]) => {\n        setState({ status: 'success', data: { user, posts, friends }, error: null })\n      })\n      .catch(err => {\n        if (err.name !== 'AbortError') setState({ status: 'error', data: null, error: err })\n      })\n\n    return () => controller.abort()\n  }, [userId])\n\n  \u002F\u002F If each request takes 200ms, total = 200ms (all run simultaneously).\n  if (state.status === 'loading') return \u003CSpinner \u002F>\n  if (state.status === 'error') return \u003CErrorMessage error={state.error} \u002F>\n  return \u003CProfile {...state.data} \u002F>\n}\n",[30,2352,2353,2358,2363,2367,2379,2405,2431,2457,2461,2471,2476,2518,2526,2567,2575,2610,2614,2619,2623,2628,2633,2637,2641,2646,2659,2691,2695,2705,2719,2737,2741,2754,2784,2812,2840,2845,2872,2889,2894,2909,2935,2940,2945,2960,2965,2970,2976,2997,3022,3041],{"__ignoreMap":28},[33,2354,2355],{"class":35,"line":36},[33,2356,2357],{"class":39},"\u002F\u002F WATERFALL: sequential fetches — each waits for the previous to complete.\n",[33,2359,2360],{"class":35,"line":43},[33,2361,2362],{"class":39},"\u002F\u002F Total time = sum of all request times. Easy to write, slow at scale.\n",[33,2364,2365],{"class":35,"line":49},[33,2366,59],{"emptyLinePlaceholder":58},[33,2368,2369,2371,2373,2375,2377],{"class":35,"line":55},[33,2370,66],{"class":65},[33,2372,70],{"class":69},[33,2374,74],{"class":73},[33,2376,78],{"class":77},[33,2378,81],{"class":73},[33,2380,2381,2383,2385,2388,2390,2393,2395,2397,2399,2401,2403],{"class":35,"line":62},[33,2382,87],{"class":65},[33,2384,90],{"class":73},[33,2386,2387],{"class":93},"user",[33,2389,97],{"class":73},[33,2391,2392],{"class":93},"setUser",[33,2394,103],{"class":73},[33,2396,106],{"class":65},[33,2398,109],{"class":69},[33,2400,184],{"class":73},[33,2402,122],{"class":93},[33,2404,195],{"class":73},[33,2406,2407,2409,2411,2414,2416,2419,2421,2423,2425,2427,2429],{"class":35,"line":84},[33,2408,87],{"class":65},[33,2410,90],{"class":73},[33,2412,2413],{"class":93},"posts",[33,2415,97],{"class":73},[33,2417,2418],{"class":93},"setPosts",[33,2420,103],{"class":73},[33,2422,106],{"class":65},[33,2424,109],{"class":69},[33,2426,184],{"class":73},[33,2428,122],{"class":93},[33,2430,195],{"class":73},[33,2432,2433,2435,2437,2440,2442,2445,2447,2449,2451,2453,2455],{"class":35,"line":133},[33,2434,87],{"class":65},[33,2436,90],{"class":73},[33,2438,2439],{"class":93},"friends",[33,2441,97],{"class":73},[33,2443,2444],{"class":93},"setFriends",[33,2446,103],{"class":73},[33,2448,106],{"class":65},[33,2450,109],{"class":69},[33,2452,184],{"class":73},[33,2454,122],{"class":93},[33,2456,195],{"class":73},[33,2458,2459],{"class":35,"line":138},[33,2460,59],{"emptyLinePlaceholder":58},[33,2462,2463,2465,2467,2469],{"class":35,"line":153},[33,2464,141],{"class":69},[33,2466,144],{"class":73},[33,2468,147],{"class":65},[33,2470,150],{"class":73},[33,2472,2473],{"class":35,"line":173},[33,2474,2475],{"class":39},"    \u002F\u002F Fetch user → wait → fetch posts → wait → fetch friends → wait\n",[33,2477,2478,2480,2482,2484,2486,2488,2491,2493,2495,2497,2499,2502,2504,2507,2509,2511,2514,2516],{"class":35,"line":178},[33,2479,181],{"class":69},[33,2481,184],{"class":73},[33,2483,187],{"class":115},[33,2485,78],{"class":73},[33,2487,192],{"class":115},[33,2489,2490],{"class":73},").",[33,2492,204],{"class":69},[33,2494,184],{"class":73},[33,2496,912],{"class":77},[33,2498,212],{"class":65},[33,2500,2501],{"class":73}," r.",[33,2503,291],{"class":69},[33,2505,2506],{"class":73},"()).",[33,2508,204],{"class":69},[33,2510,184],{"class":73},[33,2512,2513],{"class":77},"u",[33,2515,212],{"class":65},[33,2517,150],{"class":73},[33,2519,2520,2523],{"class":35,"line":198},[33,2521,2522],{"class":69},"      setUser",[33,2524,2525],{"class":73},"(u)\n",[33,2527,2528,2531,2533,2535,2537,2540,2542,2544,2546,2548,2550,2552,2554,2556,2558,2560,2563,2565],{"class":35,"line":217},[33,2529,2530],{"class":69},"      fetch",[33,2532,184],{"class":73},[33,2534,187],{"class":115},[33,2536,78],{"class":73},[33,2538,2539],{"class":115},"}\u002Fposts`",[33,2541,2490],{"class":73},[33,2543,204],{"class":69},[33,2545,184],{"class":73},[33,2547,912],{"class":77},[33,2549,212],{"class":65},[33,2551,2501],{"class":73},[33,2553,291],{"class":69},[33,2555,2506],{"class":73},[33,2557,204],{"class":69},[33,2559,184],{"class":73},[33,2561,2562],{"class":77},"p",[33,2564,212],{"class":65},[33,2566,150],{"class":73},[33,2568,2569,2572],{"class":35,"line":258},[33,2570,2571],{"class":69},"        setPosts",[33,2573,2574],{"class":73},"(p)\n",[33,2576,2577,2580,2582,2584,2586,2589,2591,2593,2595,2597,2599,2601,2603,2605,2607],{"class":35,"line":264},[33,2578,2579],{"class":69},"        fetch",[33,2581,184],{"class":73},[33,2583,187],{"class":115},[33,2585,78],{"class":73},[33,2587,2588],{"class":115},"}\u002Ffriends`",[33,2590,2490],{"class":73},[33,2592,204],{"class":69},[33,2594,184],{"class":73},[33,2596,912],{"class":77},[33,2598,212],{"class":65},[33,2600,2501],{"class":73},[33,2602,291],{"class":69},[33,2604,2506],{"class":73},[33,2606,204],{"class":69},[33,2608,2609],{"class":73},"(setFriends)\n",[33,2611,2612],{"class":35,"line":270},[33,2613,300],{"class":73},[33,2615,2616],{"class":35,"line":276},[33,2617,2618],{"class":73},"    })\n",[33,2620,2621],{"class":35,"line":282},[33,2622,365],{"class":73},[33,2624,2625],{"class":35,"line":297},[33,2626,2627],{"class":39},"  \u002F\u002F If each request takes 200ms, total = 600ms. Three independent requests\n",[33,2629,2630],{"class":35,"line":303},[33,2631,2632],{"class":39},"  \u002F\u002F that could run in parallel take 3x as long as necessary.\n",[33,2634,2635],{"class":35,"line":333},[33,2636,482],{"class":73},[33,2638,2639],{"class":35,"line":362},[33,2640,59],{"emptyLinePlaceholder":58},[33,2642,2643],{"class":35,"line":368},[33,2644,2645],{"class":39},"\u002F\u002F FIX: parallel fetching with Promise.all — total time = slowest request.\n",[33,2647,2648,2650,2653,2655,2657],{"class":35,"line":373},[33,2649,66],{"class":65},[33,2651,2652],{"class":69}," UserProfileParallel",[33,2654,74],{"class":73},[33,2656,78],{"class":77},[33,2658,81],{"class":73},[33,2660,2661,2663,2665,2667,2669,2671,2673,2675,2677,2679,2681,2683,2685,2687,2689],{"class":35,"line":403},[33,2662,87],{"class":65},[33,2664,90],{"class":73},[33,2666,94],{"class":93},[33,2668,97],{"class":73},[33,2670,100],{"class":93},[33,2672,103],{"class":73},[33,2674,106],{"class":65},[33,2676,109],{"class":69},[33,2678,112],{"class":73},[33,2680,116],{"class":115},[33,2682,119],{"class":73},[33,2684,122],{"class":93},[33,2686,125],{"class":73},[33,2688,122],{"class":93},[33,2690,130],{"class":73},[33,2692,2693],{"class":35,"line":432},[33,2694,59],{"emptyLinePlaceholder":58},[33,2696,2697,2699,2701,2703],{"class":35,"line":460},[33,2698,141],{"class":69},[33,2700,144],{"class":73},[33,2702,147],{"class":65},[33,2704,150],{"class":73},[33,2706,2707,2709,2711,2713,2715,2717],{"class":35,"line":479},[33,2708,1285],{"class":65},[33,2710,1288],{"class":93},[33,2712,1291],{"class":65},[33,2714,235],{"class":65},[33,2716,1296],{"class":69},[33,2718,294],{"class":73},[33,2720,2721,2723,2725,2727,2729,2731,2733,2735],{"class":35,"line":1490},[33,2722,156],{"class":69},[33,2724,112],{"class":73},[33,2726,116],{"class":115},[33,2728,119],{"class":73},[33,2730,122],{"class":93},[33,2732,125],{"class":73},[33,2734,122],{"class":93},[33,2736,130],{"class":73},[33,2738,2739],{"class":35,"line":1496},[33,2740,59],{"emptyLinePlaceholder":58},[33,2742,2743,2746,2748,2751],{"class":35,"line":1502},[33,2744,2745],{"class":93},"    Promise",[33,2747,248],{"class":73},[33,2749,2750],{"class":69},"all",[33,2752,2753],{"class":73},"([\n",[33,2755,2756,2758,2760,2762,2764,2766,2769,2771,2773,2775,2777,2779,2781],{"class":35,"line":2301},[33,2757,2530],{"class":69},[33,2759,184],{"class":73},[33,2761,187],{"class":115},[33,2763,78],{"class":73},[33,2765,192],{"class":115},[33,2767,2768],{"class":73},", { signal: controller.signal }).",[33,2770,204],{"class":69},[33,2772,184],{"class":73},[33,2774,912],{"class":77},[33,2776,212],{"class":65},[33,2778,2501],{"class":73},[33,2780,291],{"class":69},[33,2782,2783],{"class":73},"()),\n",[33,2785,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810],{"class":35,"line":2328},[33,2787,2530],{"class":69},[33,2789,184],{"class":73},[33,2791,187],{"class":115},[33,2793,78],{"class":73},[33,2795,2539],{"class":115},[33,2797,2768],{"class":73},[33,2799,204],{"class":69},[33,2801,184],{"class":73},[33,2803,912],{"class":77},[33,2805,212],{"class":65},[33,2807,2501],{"class":73},[33,2809,291],{"class":69},[33,2811,2783],{"class":73},[33,2813,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832,2834,2836,2838],{"class":35,"line":2333},[33,2815,2530],{"class":69},[33,2817,184],{"class":73},[33,2819,187],{"class":115},[33,2821,78],{"class":73},[33,2823,2588],{"class":115},[33,2825,2768],{"class":73},[33,2827,204],{"class":69},[33,2829,184],{"class":73},[33,2831,912],{"class":77},[33,2833,212],{"class":65},[33,2835,2501],{"class":73},[33,2837,291],{"class":69},[33,2839,2783],{"class":73},[33,2841,2842],{"class":35,"line":2338},[33,2843,2844],{"class":73},"    ])\n",[33,2846,2848,2850,2852,2855,2857,2859,2861,2863,2865,2868,2870],{"class":35,"line":2847},36,[33,2849,201],{"class":73},[33,2851,204],{"class":69},[33,2853,2854],{"class":73},"(([",[33,2856,2387],{"class":77},[33,2858,97],{"class":73},[33,2860,2413],{"class":77},[33,2862,97],{"class":73},[33,2864,2439],{"class":77},[33,2866,2867],{"class":73},"]) ",[33,2869,147],{"class":65},[33,2871,150],{"class":73},[33,2873,2875,2878,2880,2882,2885,2887],{"class":35,"line":2874},37,[33,2876,2877],{"class":69},"        setState",[33,2879,112],{"class":73},[33,2881,322],{"class":115},[33,2883,2884],{"class":73},", data: { user, posts, friends }, error: ",[33,2886,122],{"class":93},[33,2888,130],{"class":73},[33,2890,2892],{"class":35,"line":2891},38,[33,2893,300],{"class":73},[33,2895,2897,2899,2901,2903,2905,2907],{"class":35,"line":2896},39,[33,2898,201],{"class":73},[33,2900,338],{"class":69},[33,2902,184],{"class":73},[33,2904,1362],{"class":77},[33,2906,212],{"class":65},[33,2908,150],{"class":73},[33,2910,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932],{"class":35,"line":2911},40,[33,2913,220],{"class":65},[33,2915,1373],{"class":73},[33,2917,1376],{"class":65},[33,2919,1379],{"class":115},[33,2921,388],{"class":73},[33,2923,100],{"class":69},[33,2925,112],{"class":73},[33,2927,352],{"class":115},[33,2929,119],{"class":73},[33,2931,122],{"class":93},[33,2933,2934],{"class":73},", error: err })\n",[33,2936,2938],{"class":35,"line":2937},41,[33,2939,300],{"class":73},[33,2941,2943],{"class":35,"line":2942},42,[33,2944,59],{"emptyLinePlaceholder":58},[33,2946,2948,2950,2952,2954,2956,2958],{"class":35,"line":2947},43,[33,2949,1134],{"class":65},[33,2951,1137],{"class":73},[33,2953,147],{"class":65},[33,2955,1425],{"class":73},[33,2957,1428],{"class":69},[33,2959,294],{"class":73},[33,2961,2963],{"class":35,"line":2962},44,[33,2964,365],{"class":73},[33,2966,2968],{"class":35,"line":2967},45,[33,2969,59],{"emptyLinePlaceholder":58},[33,2971,2973],{"class":35,"line":2972},46,[33,2974,2975],{"class":39},"  \u002F\u002F If each request takes 200ms, total = 200ms (all run simultaneously).\n",[33,2977,2979,2981,2983,2985,2987,2989,2991,2993,2995],{"class":35,"line":2978},47,[33,2980,376],{"class":65},[33,2982,379],{"class":73},[33,2984,382],{"class":65},[33,2986,385],{"class":115},[33,2988,388],{"class":73},[33,2990,391],{"class":65},[33,2992,394],{"class":73},[33,2994,397],{"class":93},[33,2996,400],{"class":73},[33,2998,3000,3002,3004,3006,3008,3010,3012,3014,3016,3018,3020],{"class":35,"line":2999},48,[33,3001,376],{"class":65},[33,3003,379],{"class":73},[33,3005,382],{"class":65},[33,3007,412],{"class":115},[33,3009,388],{"class":73},[33,3011,391],{"class":65},[33,3013,394],{"class":73},[33,3015,421],{"class":93},[33,3017,424],{"class":69},[33,3019,106],{"class":65},[33,3021,429],{"class":73},[33,3023,3025,3027,3029,3032,3035,3038],{"class":35,"line":3024},49,[33,3026,463],{"class":65},[33,3028,394],{"class":73},[33,3030,3031],{"class":93},"Profile",[33,3033,3034],{"class":73}," {",[33,3036,3037],{"class":65},"...",[33,3039,3040],{"class":73},"state.data} \u002F>\n",[33,3042,3044],{"class":35,"line":3043},50,[33,3045,482],{"class":73},[484,3047,3049],{"id":3048},"partial-parallel-when-some-requests-depend-on-others","Partial Parallel: When Some Requests Depend on Others",[19,3051,3053],{"filename":3052,"language":22},"partial_parallel.js",[24,3054,3056],{"className":26,"code":3055,"language":22,"meta":28,"style":28},"\u002F\u002F Some requests DEPEND on data from a previous one (e.g. fetch user → get\n\u002F\u002F their team ID → fetch team members). The trick: parallelize the independent\n\u002F\u002F ones, waterfall only the dependent ones.\n\nasync function fetchDashboardData(userId) {\n  \u002F\u002F Step 1: fetch user (needed for teamId) — must wait\n  const user = await fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(r => r.json())\n\n  \u002F\u002F Step 2: fetch posts and team members in parallel (both only need userId,\n  \u002F\u002F not each other) — but teamId comes from user, so this is actually:\n  const [posts, team] = await Promise.all([\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Fposts`).then(r => r.json()),\n    fetch(`\u002Fapi\u002Fteams\u002F${user.teamId}\u002Fmembers`).then(r => r.json()),\n  ])\n\n  return { user, posts, team }\n}\n\n\u002F\u002F Even better: Promise.allSettled — if one request fails, you still get the\n\u002F\u002F others, allowing partial UI rendering instead of all-or-nothing.\nasync function fetchDashboardDataPartial(userId) {\n  const [userResult, postsResult, friendsResult] = await Promise.allSettled([\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(r => r.json()),\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Fposts`).then(r => r.json()),\n    fetch(`\u002Fapi\u002Fusers\u002F${userId}\u002Ffriends`).then(r => r.json()),\n  ])\n\n  return {\n    user: userResult.status === 'fulfilled' ? userResult.value : null,\n    posts: postsResult.status === 'fulfilled' ? postsResult.value : [],\n    friends: friendsResult.status === 'fulfilled' ? friendsResult.value : [],\n    errors: {\n      user: userResult.status === 'rejected' ? userResult.reason : null,\n      posts: postsResult.status === 'rejected' ? postsResult.reason : null,\n      friends: friendsResult.status === 'rejected' ? friendsResult.reason : null,\n    },\n  }\n}\n",[30,3057,3058,3063,3068,3073,3077,3092,3097,3133,3137,3142,3147,3174,3202,3237,3242,3246,3253,3257,3261,3266,3271,3286,3320,3348,3376,3404,3408,3412,3418,3443,3462,3480,3485,3506,3526,3546,3551,3555],{"__ignoreMap":28},[33,3059,3060],{"class":35,"line":36},[33,3061,3062],{"class":39},"\u002F\u002F Some requests DEPEND on data from a previous one (e.g. fetch user → get\n",[33,3064,3065],{"class":35,"line":43},[33,3066,3067],{"class":39},"\u002F\u002F their team ID → fetch team members). The trick: parallelize the independent\n",[33,3069,3070],{"class":35,"line":49},[33,3071,3072],{"class":39},"\u002F\u002F ones, waterfall only the dependent ones.\n",[33,3074,3075],{"class":35,"line":55},[33,3076,59],{"emptyLinePlaceholder":58},[33,3078,3079,3081,3083,3086,3088,3090],{"class":35,"line":62},[33,3080,1879],{"class":65},[33,3082,1882],{"class":65},[33,3084,3085],{"class":69}," fetchDashboardData",[33,3087,184],{"class":73},[33,3089,78],{"class":77},[33,3091,1560],{"class":73},[33,3093,3094],{"class":35,"line":84},[33,3095,3096],{"class":39},"  \u002F\u002F Step 1: fetch user (needed for teamId) — must wait\n",[33,3098,3099,3101,3103,3105,3107,3109,3111,3113,3115,3117,3119,3121,3123,3125,3127,3129,3131],{"class":35,"line":133},[33,3100,87],{"class":65},[33,3102,471],{"class":93},[33,3104,1291],{"class":65},[33,3106,1969],{"class":65},[33,3108,1972],{"class":69},[33,3110,184],{"class":73},[33,3112,187],{"class":115},[33,3114,78],{"class":73},[33,3116,192],{"class":115},[33,3118,2490],{"class":73},[33,3120,204],{"class":69},[33,3122,184],{"class":73},[33,3124,912],{"class":77},[33,3126,212],{"class":65},[33,3128,2501],{"class":73},[33,3130,291],{"class":69},[33,3132,626],{"class":73},[33,3134,3135],{"class":35,"line":138},[33,3136,59],{"emptyLinePlaceholder":58},[33,3138,3139],{"class":35,"line":153},[33,3140,3141],{"class":39},"  \u002F\u002F Step 2: fetch posts and team members in parallel (both only need userId,\n",[33,3143,3144],{"class":35,"line":173},[33,3145,3146],{"class":39},"  \u002F\u002F not each other) — but teamId comes from user, so this is actually:\n",[33,3148,3149,3151,3153,3155,3157,3160,3162,3164,3166,3168,3170,3172],{"class":35,"line":178},[33,3150,87],{"class":65},[33,3152,90],{"class":73},[33,3154,2413],{"class":93},[33,3156,97],{"class":73},[33,3158,3159],{"class":93},"team",[33,3161,103],{"class":73},[33,3163,106],{"class":65},[33,3165,1969],{"class":65},[33,3167,2081],{"class":93},[33,3169,248],{"class":73},[33,3171,2750],{"class":69},[33,3173,2753],{"class":73},[33,3175,3176,3178,3180,3182,3184,3186,3188,3190,3192,3194,3196,3198,3200],{"class":35,"line":198},[33,3177,181],{"class":69},[33,3179,184],{"class":73},[33,3181,187],{"class":115},[33,3183,78],{"class":73},[33,3185,2539],{"class":115},[33,3187,2490],{"class":73},[33,3189,204],{"class":69},[33,3191,184],{"class":73},[33,3193,912],{"class":77},[33,3195,212],{"class":65},[33,3197,2501],{"class":73},[33,3199,291],{"class":69},[33,3201,2783],{"class":73},[33,3203,3204,3206,3208,3211,3213,3215,3218,3221,3223,3225,3227,3229,3231,3233,3235],{"class":35,"line":217},[33,3205,181],{"class":69},[33,3207,184],{"class":73},[33,3209,3210],{"class":115},"`\u002Fapi\u002Fteams\u002F${",[33,3212,2387],{"class":73},[33,3214,248],{"class":115},[33,3216,3217],{"class":73},"teamId",[33,3219,3220],{"class":115},"}\u002Fmembers`",[33,3222,2490],{"class":73},[33,3224,204],{"class":69},[33,3226,184],{"class":73},[33,3228,912],{"class":77},[33,3230,212],{"class":65},[33,3232,2501],{"class":73},[33,3234,291],{"class":69},[33,3236,2783],{"class":73},[33,3238,3239],{"class":35,"line":258},[33,3240,3241],{"class":73},"  ])\n",[33,3243,3244],{"class":35,"line":264},[33,3245,59],{"emptyLinePlaceholder":58},[33,3247,3248,3250],{"class":35,"line":270},[33,3249,463],{"class":65},[33,3251,3252],{"class":73}," { user, posts, team }\n",[33,3254,3255],{"class":35,"line":276},[33,3256,482],{"class":73},[33,3258,3259],{"class":35,"line":282},[33,3260,59],{"emptyLinePlaceholder":58},[33,3262,3263],{"class":35,"line":297},[33,3264,3265],{"class":39},"\u002F\u002F Even better: Promise.allSettled — if one request fails, you still get the\n",[33,3267,3268],{"class":35,"line":303},[33,3269,3270],{"class":39},"\u002F\u002F others, allowing partial UI rendering instead of all-or-nothing.\n",[33,3272,3273,3275,3277,3280,3282,3284],{"class":35,"line":333},[33,3274,1879],{"class":65},[33,3276,1882],{"class":65},[33,3278,3279],{"class":69}," fetchDashboardDataPartial",[33,3281,184],{"class":73},[33,3283,78],{"class":77},[33,3285,1560],{"class":73},[33,3287,3288,3290,3292,3295,3297,3300,3302,3305,3307,3309,3311,3313,3315,3318],{"class":35,"line":362},[33,3289,87],{"class":65},[33,3291,90],{"class":73},[33,3293,3294],{"class":93},"userResult",[33,3296,97],{"class":73},[33,3298,3299],{"class":93},"postsResult",[33,3301,97],{"class":73},[33,3303,3304],{"class":93},"friendsResult",[33,3306,103],{"class":73},[33,3308,106],{"class":65},[33,3310,1969],{"class":65},[33,3312,2081],{"class":93},[33,3314,248],{"class":73},[33,3316,3317],{"class":69},"allSettled",[33,3319,2753],{"class":73},[33,3321,3322,3324,3326,3328,3330,3332,3334,3336,3338,3340,3342,3344,3346],{"class":35,"line":368},[33,3323,181],{"class":69},[33,3325,184],{"class":73},[33,3327,187],{"class":115},[33,3329,78],{"class":73},[33,3331,192],{"class":115},[33,3333,2490],{"class":73},[33,3335,204],{"class":69},[33,3337,184],{"class":73},[33,3339,912],{"class":77},[33,3341,212],{"class":65},[33,3343,2501],{"class":73},[33,3345,291],{"class":69},[33,3347,2783],{"class":73},[33,3349,3350,3352,3354,3356,3358,3360,3362,3364,3366,3368,3370,3372,3374],{"class":35,"line":373},[33,3351,181],{"class":69},[33,3353,184],{"class":73},[33,3355,187],{"class":115},[33,3357,78],{"class":73},[33,3359,2539],{"class":115},[33,3361,2490],{"class":73},[33,3363,204],{"class":69},[33,3365,184],{"class":73},[33,3367,912],{"class":77},[33,3369,212],{"class":65},[33,3371,2501],{"class":73},[33,3373,291],{"class":69},[33,3375,2783],{"class":73},[33,3377,3378,3380,3382,3384,3386,3388,3390,3392,3394,3396,3398,3400,3402],{"class":35,"line":403},[33,3379,181],{"class":69},[33,3381,184],{"class":73},[33,3383,187],{"class":115},[33,3385,78],{"class":73},[33,3387,2588],{"class":115},[33,3389,2490],{"class":73},[33,3391,204],{"class":69},[33,3393,184],{"class":73},[33,3395,912],{"class":77},[33,3397,212],{"class":65},[33,3399,2501],{"class":73},[33,3401,291],{"class":69},[33,3403,2783],{"class":73},[33,3405,3406],{"class":35,"line":432},[33,3407,3241],{"class":73},[33,3409,3410],{"class":35,"line":460},[33,3411,59],{"emptyLinePlaceholder":58},[33,3413,3414,3416],{"class":35,"line":479},[33,3415,463],{"class":65},[33,3417,150],{"class":73},[33,3419,3420,3423,3425,3428,3431,3434,3437,3440],{"class":35,"line":1490},[33,3421,3422],{"class":73},"    user: userResult.status ",[33,3424,382],{"class":65},[33,3426,3427],{"class":115}," 'fulfilled'",[33,3429,3430],{"class":65}," ?",[33,3432,3433],{"class":73}," userResult.value ",[33,3435,3436],{"class":65},":",[33,3438,3439],{"class":93}," null",[33,3441,3442],{"class":73},",\n",[33,3444,3445,3448,3450,3452,3454,3457,3459],{"class":35,"line":1496},[33,3446,3447],{"class":73},"    posts: postsResult.status ",[33,3449,382],{"class":65},[33,3451,3427],{"class":115},[33,3453,3430],{"class":65},[33,3455,3456],{"class":73}," postsResult.value ",[33,3458,3436],{"class":65},[33,3460,3461],{"class":73}," [],\n",[33,3463,3464,3467,3469,3471,3473,3476,3478],{"class":35,"line":1502},[33,3465,3466],{"class":73},"    friends: friendsResult.status ",[33,3468,382],{"class":65},[33,3470,3427],{"class":115},[33,3472,3430],{"class":65},[33,3474,3475],{"class":73}," friendsResult.value ",[33,3477,3436],{"class":65},[33,3479,3461],{"class":73},[33,3481,3482],{"class":35,"line":2301},[33,3483,3484],{"class":73},"    errors: {\n",[33,3486,3487,3490,3492,3495,3497,3500,3502,3504],{"class":35,"line":2328},[33,3488,3489],{"class":73},"      user: userResult.status ",[33,3491,382],{"class":65},[33,3493,3494],{"class":115}," 'rejected'",[33,3496,3430],{"class":65},[33,3498,3499],{"class":73}," userResult.reason ",[33,3501,3436],{"class":65},[33,3503,3439],{"class":93},[33,3505,3442],{"class":73},[33,3507,3508,3511,3513,3515,3517,3520,3522,3524],{"class":35,"line":2333},[33,3509,3510],{"class":73},"      posts: postsResult.status ",[33,3512,382],{"class":65},[33,3514,3494],{"class":115},[33,3516,3430],{"class":65},[33,3518,3519],{"class":73}," postsResult.reason ",[33,3521,3436],{"class":65},[33,3523,3439],{"class":93},[33,3525,3442],{"class":73},[33,3527,3528,3531,3533,3535,3537,3540,3542,3544],{"class":35,"line":2338},[33,3529,3530],{"class":73},"      friends: friendsResult.status ",[33,3532,382],{"class":65},[33,3534,3494],{"class":115},[33,3536,3430],{"class":65},[33,3538,3539],{"class":73}," friendsResult.reason ",[33,3541,3436],{"class":65},[33,3543,3439],{"class":93},[33,3545,3442],{"class":73},[33,3547,3548],{"class":35,"line":2847},[33,3549,3550],{"class":73},"    },\n",[33,3552,3553],{"class":35,"line":2874},[33,3554,2114],{"class":73},[33,3556,3557],{"class":35,"line":2891},[33,3558,482],{"class":73},[14,3560,3562],{"id":3561},"react-query-tanstack-query-the-production-standard","React Query (TanStack Query): The Production Standard",[19,3564,3566],{"filename":3565,"language":22},"react_query.js",[24,3567,3569],{"className":26,"code":3568,"language":22,"meta":28,"style":28},"\u002F\u002F Every pattern above — status tracking, race protection, aborting, delayed\n\u002F\u002F loading, retries — is correct, and still doesn't cover caching, refetch-on-\n\u002F\u002F focus, request deduplication, or pagination\u002Finfinite-scroll data merging.\n\u002F\u002F React Query (now TanStack Query) solves ALL of this.\n\nimport { useQuery, useMutation, useQueryClient } from '@tanstack\u002Freact-query'\n\nfunction UserProfile({ userId }) {\n  const { data, status, error, isFetching } = useQuery({\n    queryKey: ['user', userId],\n    queryFn: async () => {\n      const res = await fetch(`\u002Fapi\u002Fusers\u002F${userId}`)\n      if (!res.ok) throw new Error(`HTTP ${res.status}`)\n      return res.json()\n    },\n    \u002F\u002F The queryKey is the mechanism that gives all the \"hard parts\" for free:\n    \u002F\u002F - DEDUP: concurrent requests for the same key are merged into one fetch\n    \u002F\u002F - CACHE: a second component mounting with the same key gets cached data instantly\n    \u002F\u002F - RACE CONDITIONS: automatically handled — key changes → old query discarded\n    \u002F\u002F - REFETCH: automatically on window refocus or network reconnect (configurable)\n  })\n\n  if (status === 'pending') return \u003CSpinner \u002F>\n  if (status === 'error') return \u003CErrorMessage error={error} \u002F>\n  \u002F\u002F isFetching is true during background refetches even when data is already shown\n  \u002F\u002F — use it for a subtle \"refreshing\" indicator, not a full spinner.\n  return (\n    \u003Cdiv>\n      {isFetching && \u003CRefreshIndicator \u002F>}\n      \u003CProfileCard user={data} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,3570,3571,3576,3581,3586,3591,3595,3609,3613,3625,3656,3667,3683,3705,3735,3745,3749,3754,3759,3764,3769,3774,3779,3783,3804,3829,3834,3839,3846,3856,3872,3885,3894,3899],{"__ignoreMap":28},[33,3572,3573],{"class":35,"line":36},[33,3574,3575],{"class":39},"\u002F\u002F Every pattern above — status tracking, race protection, aborting, delayed\n",[33,3577,3578],{"class":35,"line":43},[33,3579,3580],{"class":39},"\u002F\u002F loading, retries — is correct, and still doesn't cover caching, refetch-on-\n",[33,3582,3583],{"class":35,"line":49},[33,3584,3585],{"class":39},"\u002F\u002F focus, request deduplication, or pagination\u002Finfinite-scroll data merging.\n",[33,3587,3588],{"class":35,"line":55},[33,3589,3590],{"class":39},"\u002F\u002F React Query (now TanStack Query) solves ALL of this.\n",[33,3592,3593],{"class":35,"line":62},[33,3594,59],{"emptyLinePlaceholder":58},[33,3596,3597,3600,3603,3606],{"class":35,"line":84},[33,3598,3599],{"class":65},"import",[33,3601,3602],{"class":73}," { useQuery, useMutation, useQueryClient } ",[33,3604,3605],{"class":65},"from",[33,3607,3608],{"class":115}," '@tanstack\u002Freact-query'\n",[33,3610,3611],{"class":35,"line":133},[33,3612,59],{"emptyLinePlaceholder":58},[33,3614,3615,3617,3619,3621,3623],{"class":35,"line":138},[33,3616,66],{"class":65},[33,3618,70],{"class":69},[33,3620,74],{"class":73},[33,3622,78],{"class":77},[33,3624,81],{"class":73},[33,3626,3627,3629,3631,3633,3635,3637,3639,3641,3643,3646,3648,3650,3653],{"class":35,"line":153},[33,3628,87],{"class":65},[33,3630,641],{"class":73},[33,3632,312],{"class":93},[33,3634,97],{"class":73},[33,3636,251],{"class":93},[33,3638,97],{"class":73},[33,3640,343],{"class":93},[33,3642,97],{"class":73},[33,3644,3645],{"class":93},"isFetching",[33,3647,1744],{"class":73},[33,3649,106],{"class":65},[33,3651,3652],{"class":69}," useQuery",[33,3654,3655],{"class":73},"({\n",[33,3657,3658,3661,3664],{"class":35,"line":173},[33,3659,3660],{"class":73},"    queryKey: [",[33,3662,3663],{"class":115},"'user'",[33,3665,3666],{"class":73},", userId],\n",[33,3668,3669,3672,3675,3677,3679,3681],{"class":35,"line":178},[33,3670,3671],{"class":69},"    queryFn",[33,3673,3674],{"class":73},": ",[33,3676,1879],{"class":65},[33,3678,1137],{"class":73},[33,3680,147],{"class":65},[33,3682,150],{"class":73},[33,3684,3685,3687,3689,3691,3693,3695,3697,3699,3701,3703],{"class":35,"line":198},[33,3686,1961],{"class":65},[33,3688,1964],{"class":93},[33,3690,1291],{"class":65},[33,3692,1969],{"class":65},[33,3694,1972],{"class":69},[33,3696,184],{"class":73},[33,3698,187],{"class":115},[33,3700,78],{"class":73},[33,3702,192],{"class":115},[33,3704,195],{"class":73},[33,3706,3707,3709,3711,3713,3715,3717,3719,3721,3723,3725,3727,3729,3731,3733],{"class":35,"line":217},[33,3708,1980],{"class":65},[33,3710,223],{"class":73},[33,3712,226],{"class":65},[33,3714,229],{"class":73},[33,3716,232],{"class":65},[33,3718,235],{"class":65},[33,3720,238],{"class":69},[33,3722,184],{"class":73},[33,3724,243],{"class":115},[33,3726,209],{"class":73},[33,3728,248],{"class":115},[33,3730,251],{"class":73},[33,3732,192],{"class":115},[33,3734,195],{"class":73},[33,3736,3737,3739,3741,3743],{"class":35,"line":258},[33,3738,2011],{"class":65},[33,3740,288],{"class":73},[33,3742,291],{"class":69},[33,3744,294],{"class":73},[33,3746,3747],{"class":35,"line":264},[33,3748,3550],{"class":73},[33,3750,3751],{"class":35,"line":270},[33,3752,3753],{"class":39},"    \u002F\u002F The queryKey is the mechanism that gives all the \"hard parts\" for free:\n",[33,3755,3756],{"class":35,"line":276},[33,3757,3758],{"class":39},"    \u002F\u002F - DEDUP: concurrent requests for the same key are merged into one fetch\n",[33,3760,3761],{"class":35,"line":282},[33,3762,3763],{"class":39},"    \u002F\u002F - CACHE: a second component mounting with the same key gets cached data instantly\n",[33,3765,3766],{"class":35,"line":297},[33,3767,3768],{"class":39},"    \u002F\u002F - RACE CONDITIONS: automatically handled — key changes → old query discarded\n",[33,3770,3771],{"class":35,"line":303},[33,3772,3773],{"class":39},"    \u002F\u002F - REFETCH: automatically on window refocus or network reconnect (configurable)\n",[33,3775,3776],{"class":35,"line":333},[33,3777,3778],{"class":73},"  })\n",[33,3780,3781],{"class":35,"line":362},[33,3782,59],{"emptyLinePlaceholder":58},[33,3784,3785,3787,3789,3791,3794,3796,3798,3800,3802],{"class":35,"line":368},[33,3786,376],{"class":65},[33,3788,1810],{"class":73},[33,3790,382],{"class":65},[33,3792,3793],{"class":115}," 'pending'",[33,3795,388],{"class":73},[33,3797,391],{"class":65},[33,3799,394],{"class":73},[33,3801,397],{"class":93},[33,3803,400],{"class":73},[33,3805,3806,3808,3810,3812,3814,3816,3818,3820,3822,3824,3826],{"class":35,"line":373},[33,3807,376],{"class":65},[33,3809,1810],{"class":73},[33,3811,382],{"class":65},[33,3813,412],{"class":115},[33,3815,388],{"class":73},[33,3817,391],{"class":65},[33,3819,394],{"class":73},[33,3821,421],{"class":93},[33,3823,424],{"class":69},[33,3825,106],{"class":65},[33,3827,3828],{"class":73},"{error} \u002F>\n",[33,3830,3831],{"class":35,"line":403},[33,3832,3833],{"class":39},"  \u002F\u002F isFetching is true during background refetches even when data is already shown\n",[33,3835,3836],{"class":35,"line":432},[33,3837,3838],{"class":39},"  \u002F\u002F — use it for a subtle \"refreshing\" indicator, not a full spinner.\n",[33,3840,3841,3843],{"class":35,"line":460},[33,3842,463],{"class":65},[33,3844,3845],{"class":73}," (\n",[33,3847,3848,3851,3854],{"class":35,"line":479},[33,3849,3850],{"class":73},"    \u003C",[33,3852,3853],{"class":900},"div",[33,3855,937],{"class":73},[33,3857,3858,3861,3864,3866,3869],{"class":35,"line":1490},[33,3859,3860],{"class":73},"      {isFetching ",[33,3862,3863],{"class":65},"&&",[33,3865,394],{"class":73},[33,3867,3868],{"class":93},"RefreshIndicator",[33,3870,3871],{"class":73}," \u002F>}\n",[33,3873,3874,3877,3879,3881,3883],{"class":35,"line":1496},[33,3875,3876],{"class":73},"      \u003C",[33,3878,468],{"class":93},[33,3880,471],{"class":69},[33,3882,106],{"class":65},[33,3884,1839],{"class":73},[33,3886,3887,3890,3892],{"class":35,"line":1502},[33,3888,3889],{"class":73},"    \u003C\u002F",[33,3891,3853],{"class":900},[33,3893,937],{"class":73},[33,3895,3896],{"class":35,"line":2301},[33,3897,3898],{"class":73},"  )\n",[33,3900,3901],{"class":35,"line":2328},[33,3902,482],{"class":73},[484,3904,3906],{"id":3905},"mutations-and-cache-invalidation","Mutations and Cache Invalidation",[19,3908,3910],{"filename":3909,"language":22},"react_query_mutations.js",[24,3911,3913],{"className":26,"code":3912,"language":22,"meta":28,"style":28},"import { useMutation, useQueryClient } from '@tanstack\u002Freact-query'\n\nfunction EditProfileForm({ userId }) {\n  const queryClient = useQueryClient()\n\n  const mutation = useMutation({\n    mutationFn: (updates) => fetch(`\u002Fapi\u002Fusers\u002F${userId}`, {\n      method: 'PATCH',\n      headers: { 'Content-Type': 'application\u002Fjson' },\n      body: JSON.stringify(updates),\n    }),\n    onSuccess: () => {\n      \u002F\u002F Invalidate the cached query so every component reading it refetches\n      \u002F\u002F fresh data — no manual state synchronization needed.\n      queryClient.invalidateQueries({ queryKey: ['user', userId] })\n    },\n  })\n\n  function handleSubmit(e) {\n    e.preventDefault()\n    mutation.mutate({ name: e.target.name.value })\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"name\" defaultValue=\"\" \u002F>\n      \u003Cbutton type=\"submit\" disabled={mutation.isPending}>\n        {mutation.isPending ? 'Saving…' : 'Save'}\n      \u003C\u002Fbutton>\n      {mutation.isError && \u003Cp className=\"error\">{mutation.error.message}\u003C\u002Fp>}\n      {mutation.isSuccess && \u003Cp className=\"success\">Saved!\u003C\u002Fp>}\n    \u003C\u002Fform>\n  )\n}\n",[30,3914,3915,3926,3930,3943,3957,3961,3975,4003,4013,4029,4045,4050,4062,4067,4072,4088,4092,4096,4100,4115,4125,4136,4140,4144,4150,4165,4190,4213,4232,4241,4267,4292,4300,4304],{"__ignoreMap":28},[33,3916,3917,3919,3922,3924],{"class":35,"line":36},[33,3918,3599],{"class":65},[33,3920,3921],{"class":73}," { useMutation, useQueryClient } ",[33,3923,3605],{"class":65},[33,3925,3608],{"class":115},[33,3927,3928],{"class":35,"line":43},[33,3929,59],{"emptyLinePlaceholder":58},[33,3931,3932,3934,3937,3939,3941],{"class":35,"line":49},[33,3933,66],{"class":65},[33,3935,3936],{"class":69}," EditProfileForm",[33,3938,74],{"class":73},[33,3940,78],{"class":77},[33,3942,81],{"class":73},[33,3944,3945,3947,3950,3952,3955],{"class":35,"line":55},[33,3946,87],{"class":65},[33,3948,3949],{"class":93}," queryClient",[33,3951,1291],{"class":65},[33,3953,3954],{"class":69}," useQueryClient",[33,3956,294],{"class":73},[33,3958,3959],{"class":35,"line":62},[33,3960,59],{"emptyLinePlaceholder":58},[33,3962,3963,3965,3968,3970,3973],{"class":35,"line":84},[33,3964,87],{"class":65},[33,3966,3967],{"class":93}," mutation",[33,3969,1291],{"class":65},[33,3971,3972],{"class":69}," useMutation",[33,3974,3655],{"class":73},[33,3976,3977,3980,3983,3986,3988,3990,3992,3994,3996,3998,4000],{"class":35,"line":133},[33,3978,3979],{"class":69},"    mutationFn",[33,3981,3982],{"class":73},": (",[33,3984,3985],{"class":77},"updates",[33,3987,388],{"class":73},[33,3989,147],{"class":65},[33,3991,1972],{"class":69},[33,3993,184],{"class":73},[33,3995,187],{"class":115},[33,3997,78],{"class":73},[33,3999,192],{"class":115},[33,4001,4002],{"class":73},", {\n",[33,4004,4005,4008,4011],{"class":35,"line":138},[33,4006,4007],{"class":73},"      method: ",[33,4009,4010],{"class":115},"'PATCH'",[33,4012,3442],{"class":73},[33,4014,4015,4018,4021,4023,4026],{"class":35,"line":153},[33,4016,4017],{"class":73},"      headers: { ",[33,4019,4020],{"class":115},"'Content-Type'",[33,4022,3674],{"class":73},[33,4024,4025],{"class":115},"'application\u002Fjson'",[33,4027,4028],{"class":73}," },\n",[33,4030,4031,4034,4037,4039,4042],{"class":35,"line":173},[33,4032,4033],{"class":73},"      body: ",[33,4035,4036],{"class":93},"JSON",[33,4038,248],{"class":73},[33,4040,4041],{"class":69},"stringify",[33,4043,4044],{"class":73},"(updates),\n",[33,4046,4047],{"class":35,"line":178},[33,4048,4049],{"class":73},"    }),\n",[33,4051,4052,4055,4058,4060],{"class":35,"line":198},[33,4053,4054],{"class":69},"    onSuccess",[33,4056,4057],{"class":73},": () ",[33,4059,147],{"class":65},[33,4061,150],{"class":73},[33,4063,4064],{"class":35,"line":217},[33,4065,4066],{"class":39},"      \u002F\u002F Invalidate the cached query so every component reading it refetches\n",[33,4068,4069],{"class":35,"line":258},[33,4070,4071],{"class":39},"      \u002F\u002F fresh data — no manual state synchronization needed.\n",[33,4073,4074,4077,4080,4083,4085],{"class":35,"line":264},[33,4075,4076],{"class":73},"      queryClient.",[33,4078,4079],{"class":69},"invalidateQueries",[33,4081,4082],{"class":73},"({ queryKey: [",[33,4084,3663],{"class":115},[33,4086,4087],{"class":73},", userId] })\n",[33,4089,4090],{"class":35,"line":270},[33,4091,3550],{"class":73},[33,4093,4094],{"class":35,"line":276},[33,4095,3778],{"class":73},[33,4097,4098],{"class":35,"line":282},[33,4099,59],{"emptyLinePlaceholder":58},[33,4101,4102,4105,4108,4110,4113],{"class":35,"line":297},[33,4103,4104],{"class":65},"  function",[33,4106,4107],{"class":69}," handleSubmit",[33,4109,184],{"class":73},[33,4111,4112],{"class":77},"e",[33,4114,1560],{"class":73},[33,4116,4117,4120,4123],{"class":35,"line":303},[33,4118,4119],{"class":73},"    e.",[33,4121,4122],{"class":69},"preventDefault",[33,4124,294],{"class":73},[33,4126,4127,4130,4133],{"class":35,"line":333},[33,4128,4129],{"class":73},"    mutation.",[33,4131,4132],{"class":69},"mutate",[33,4134,4135],{"class":73},"({ name: e.target.name.value })\n",[33,4137,4138],{"class":35,"line":362},[33,4139,2114],{"class":73},[33,4141,4142],{"class":35,"line":368},[33,4143,59],{"emptyLinePlaceholder":58},[33,4145,4146,4148],{"class":35,"line":373},[33,4147,463],{"class":65},[33,4149,3845],{"class":73},[33,4151,4152,4154,4157,4160,4162],{"class":35,"line":403},[33,4153,3850],{"class":73},[33,4155,4156],{"class":900},"form",[33,4158,4159],{"class":69}," onSubmit",[33,4161,106],{"class":65},[33,4163,4164],{"class":73},"{handleSubmit}>\n",[33,4166,4167,4169,4172,4175,4177,4180,4183,4185,4188],{"class":35,"line":432},[33,4168,3876],{"class":73},[33,4170,4171],{"class":900},"input",[33,4173,4174],{"class":69}," name",[33,4176,106],{"class":65},[33,4178,4179],{"class":115},"\"name\"",[33,4181,4182],{"class":69}," defaultValue",[33,4184,106],{"class":65},[33,4186,4187],{"class":115},"\"\"",[33,4189,400],{"class":73},[33,4191,4192,4194,4197,4200,4202,4205,4208,4210],{"class":35,"line":460},[33,4193,3876],{"class":73},[33,4195,4196],{"class":900},"button",[33,4198,4199],{"class":69}," type",[33,4201,106],{"class":65},[33,4203,4204],{"class":115},"\"submit\"",[33,4206,4207],{"class":69}," disabled",[33,4209,106],{"class":65},[33,4211,4212],{"class":73},"{mutation.isPending}>\n",[33,4214,4215,4218,4221,4224,4227,4230],{"class":35,"line":479},[33,4216,4217],{"class":73},"        {mutation.isPending ",[33,4219,4220],{"class":65},"?",[33,4222,4223],{"class":115}," 'Saving…'",[33,4225,4226],{"class":65}," :",[33,4228,4229],{"class":115}," 'Save'",[33,4231,482],{"class":73},[33,4233,4234,4237,4239],{"class":35,"line":1490},[33,4235,4236],{"class":73},"      \u003C\u002F",[33,4238,4196],{"class":900},[33,4240,937],{"class":73},[33,4242,4243,4246,4248,4250,4252,4255,4257,4260,4263,4265],{"class":35,"line":1496},[33,4244,4245],{"class":73},"      {mutation.isError ",[33,4247,3863],{"class":65},[33,4249,394],{"class":73},[33,4251,2562],{"class":900},[33,4253,4254],{"class":69}," className",[33,4256,106],{"class":65},[33,4258,4259],{"class":115},"\"error\"",[33,4261,4262],{"class":73},">{mutation.error.message}\u003C\u002F",[33,4264,2562],{"class":900},[33,4266,1195],{"class":73},[33,4268,4269,4272,4274,4276,4278,4280,4282,4285,4288,4290],{"class":35,"line":1502},[33,4270,4271],{"class":73},"      {mutation.isSuccess ",[33,4273,3863],{"class":65},[33,4275,394],{"class":73},[33,4277,2562],{"class":900},[33,4279,4254],{"class":69},[33,4281,106],{"class":65},[33,4283,4284],{"class":115},"\"success\"",[33,4286,4287],{"class":73},">Saved!\u003C\u002F",[33,4289,2562],{"class":900},[33,4291,1195],{"class":73},[33,4293,4294,4296,4298],{"class":35,"line":2301},[33,4295,3889],{"class":73},[33,4297,4156],{"class":900},[33,4299,937],{"class":73},[33,4301,4302],{"class":35,"line":2328},[33,4303,3898],{"class":73},[33,4305,4306],{"class":35,"line":2333},[33,4307,482],{"class":73},[484,4309,4311],{"id":4310},"caching-strategies-with-react-query","Caching Strategies with React Query",[19,4313,4315],{"filename":4314,"language":22},"query_caching.js",[24,4316,4318],{"className":26,"code":4317,"language":22,"meta":28,"style":28},"\u002F\u002F React Query's caching is configurable per query via the options object.\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 60 * 1000,      \u002F\u002F data is fresh for 1 minute — no refetch on focus\n      gcTime: 5 * 60 * 1000,     \u002F\u002F garbage-collect unused cache after 5 minutes\n      retry: 3,                   \u002F\u002F retry failed requests 3 times\n      refetchOnWindowFocus: true, \u002F\u002F refetch when the user returns to the tab\n      refetchOnReconnect: true,   \u002F\u002F refetch when network reconnects\n    },\n  },\n})\n\n\u002F\u002F Per-query overrides:\nfunction ProductList({ category }) {\n  const { data } = useQuery({\n    queryKey: ['products', category],\n    queryFn: () => fetch(`\u002Fapi\u002Fproducts?cat=${category}`).then(r => r.json()),\n    staleTime: 5 * 60 * 1000,  \u002F\u002F products change rarely — 5 min stale time\n    refetchOnWindowFocus: false,  \u002F\u002F don't refetch on focus (products don't change that fast)\n  })\n  return \u003CProductGrid products={data} \u002F>\n}\n\nfunction LiveScores() {\n  const { data } = useQuery({\n    queryKey: ['scores'],\n    queryFn: () => fetch('\u002Fapi\u002Fscores').then(r => r.json()),\n    refetchInterval: 10 * 1000,  \u002F\u002F poll every 10 seconds (live data)\n    staleTime: 0,                \u002F\u002F always stale → always refetch on focus\n  })\n  return \u003CScoreboard scores={data} \u002F>\n}\n\n\u002F\u002F OPTIMISTIC UPDATES: update the cache immediately, rollback on error\nfunction useOptimisticUpdate() {\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: (newTodo) => fetch('\u002Fapi\u002Ftodos', { method: 'POST', body: JSON.stringify(newTodo) }),\n    onMutate: async (newTodo) => {\n      await queryClient.cancelQueries({ queryKey: ['todos'] })\n      const previousTodos = queryClient.getQueryData(['todos'])\n      queryClient.setQueryData(['todos'], (old) => [...old, newTodo])  \u002F\u002F optimistic add\n      return { previousTodos }  \u002F\u002F context for rollback\n    },\n    onError: (err, newTodo, context) => {\n      queryClient.setQueryData(['todos'], context.previousTodos)  \u002F\u002F rollback on error\n    },\n    onSettled: () => {\n      queryClient.invalidateQueries({ queryKey: ['todos'] })  \u002F\u002F always refetch after\n    },\n  })\n}\n",[30,4319,4320,4325,4329,4345,4350,4355,4375,4398,4412,4424,4437,4441,4446,4451,4455,4460,4474,4490,4500,4535,4556,4568,4572,4588,4592,4596,4606,4622,4632,4663,4680,4693,4697,4713,4717,4721,4726,4735,4747,4751,4759,4797,4816,4834,4856,4887,4897,4901,4925,4941,4945,4957,4974,4979,4984],{"__ignoreMap":28},[33,4321,4322],{"class":35,"line":36},[33,4323,4324],{"class":39},"\u002F\u002F React Query's caching is configurable per query via the options object.\n",[33,4326,4327],{"class":35,"line":43},[33,4328,59],{"emptyLinePlaceholder":58},[33,4330,4331,4334,4336,4338,4340,4343],{"class":35,"line":49},[33,4332,4333],{"class":65},"const",[33,4335,3949],{"class":93},[33,4337,1291],{"class":65},[33,4339,235],{"class":65},[33,4341,4342],{"class":69}," QueryClient",[33,4344,3655],{"class":73},[33,4346,4347],{"class":35,"line":55},[33,4348,4349],{"class":73},"  defaultOptions: {\n",[33,4351,4352],{"class":35,"line":62},[33,4353,4354],{"class":73},"    queries: {\n",[33,4356,4357,4360,4363,4366,4369,4372],{"class":35,"line":84},[33,4358,4359],{"class":73},"      staleTime: ",[33,4361,4362],{"class":93},"60",[33,4364,4365],{"class":65}," *",[33,4367,4368],{"class":93}," 1000",[33,4370,4371],{"class":73},",      ",[33,4373,4374],{"class":39},"\u002F\u002F data is fresh for 1 minute — no refetch on focus\n",[33,4376,4377,4380,4383,4385,4388,4390,4392,4395],{"class":35,"line":133},[33,4378,4379],{"class":73},"      gcTime: ",[33,4381,4382],{"class":93},"5",[33,4384,4365],{"class":65},[33,4386,4387],{"class":93}," 60",[33,4389,4365],{"class":65},[33,4391,4368],{"class":93},[33,4393,4394],{"class":73},",     ",[33,4396,4397],{"class":39},"\u002F\u002F garbage-collect unused cache after 5 minutes\n",[33,4399,4400,4403,4406,4409],{"class":35,"line":138},[33,4401,4402],{"class":73},"      retry: ",[33,4404,4405],{"class":93},"3",[33,4407,4408],{"class":73},",                   ",[33,4410,4411],{"class":39},"\u002F\u002F retry failed requests 3 times\n",[33,4413,4414,4417,4419,4421],{"class":35,"line":153},[33,4415,4416],{"class":73},"      refetchOnWindowFocus: ",[33,4418,577],{"class":93},[33,4420,97],{"class":73},[33,4422,4423],{"class":39},"\u002F\u002F refetch when the user returns to the tab\n",[33,4425,4426,4429,4431,4434],{"class":35,"line":173},[33,4427,4428],{"class":73},"      refetchOnReconnect: ",[33,4430,577],{"class":93},[33,4432,4433],{"class":73},",   ",[33,4435,4436],{"class":39},"\u002F\u002F refetch when network reconnects\n",[33,4438,4439],{"class":35,"line":178},[33,4440,3550],{"class":73},[33,4442,4443],{"class":35,"line":198},[33,4444,4445],{"class":73},"  },\n",[33,4447,4448],{"class":35,"line":217},[33,4449,4450],{"class":73},"})\n",[33,4452,4453],{"class":35,"line":258},[33,4454,59],{"emptyLinePlaceholder":58},[33,4456,4457],{"class":35,"line":264},[33,4458,4459],{"class":39},"\u002F\u002F Per-query overrides:\n",[33,4461,4462,4464,4467,4469,4472],{"class":35,"line":270},[33,4463,66],{"class":65},[33,4465,4466],{"class":69}," ProductList",[33,4468,74],{"class":73},[33,4470,4471],{"class":77},"category",[33,4473,81],{"class":73},[33,4475,4476,4478,4480,4482,4484,4486,4488],{"class":35,"line":276},[33,4477,87],{"class":65},[33,4479,641],{"class":73},[33,4481,312],{"class":93},[33,4483,1744],{"class":73},[33,4485,106],{"class":65},[33,4487,3652],{"class":69},[33,4489,3655],{"class":73},[33,4491,4492,4494,4497],{"class":35,"line":282},[33,4493,3660],{"class":73},[33,4495,4496],{"class":115},"'products'",[33,4498,4499],{"class":73},", category],\n",[33,4501,4502,4504,4506,4508,4510,4512,4515,4517,4519,4521,4523,4525,4527,4529,4531,4533],{"class":35,"line":297},[33,4503,3671],{"class":69},[33,4505,4057],{"class":73},[33,4507,147],{"class":65},[33,4509,1972],{"class":69},[33,4511,184],{"class":73},[33,4513,4514],{"class":115},"`\u002Fapi\u002Fproducts?cat=${",[33,4516,4471],{"class":73},[33,4518,192],{"class":115},[33,4520,2490],{"class":73},[33,4522,204],{"class":69},[33,4524,184],{"class":73},[33,4526,912],{"class":77},[33,4528,212],{"class":65},[33,4530,2501],{"class":73},[33,4532,291],{"class":69},[33,4534,2783],{"class":73},[33,4536,4537,4540,4542,4544,4546,4548,4550,4553],{"class":35,"line":303},[33,4538,4539],{"class":73},"    staleTime: ",[33,4541,4382],{"class":93},[33,4543,4365],{"class":65},[33,4545,4387],{"class":93},[33,4547,4365],{"class":65},[33,4549,4368],{"class":93},[33,4551,4552],{"class":73},",  ",[33,4554,4555],{"class":39},"\u002F\u002F products change rarely — 5 min stale time\n",[33,4557,4558,4561,4563,4565],{"class":35,"line":333},[33,4559,4560],{"class":73},"    refetchOnWindowFocus: ",[33,4562,653],{"class":93},[33,4564,4552],{"class":73},[33,4566,4567],{"class":39},"\u002F\u002F don't refetch on focus (products don't change that fast)\n",[33,4569,4570],{"class":35,"line":362},[33,4571,3778],{"class":73},[33,4573,4574,4576,4578,4581,4584,4586],{"class":35,"line":368},[33,4575,463],{"class":65},[33,4577,394],{"class":73},[33,4579,4580],{"class":93},"ProductGrid",[33,4582,4583],{"class":69}," products",[33,4585,106],{"class":65},[33,4587,1839],{"class":73},[33,4589,4590],{"class":35,"line":373},[33,4591,482],{"class":73},[33,4593,4594],{"class":35,"line":403},[33,4595,59],{"emptyLinePlaceholder":58},[33,4597,4598,4600,4603],{"class":35,"line":432},[33,4599,66],{"class":65},[33,4601,4602],{"class":69}," LiveScores",[33,4604,4605],{"class":73},"() {\n",[33,4607,4608,4610,4612,4614,4616,4618,4620],{"class":35,"line":460},[33,4609,87],{"class":65},[33,4611,641],{"class":73},[33,4613,312],{"class":93},[33,4615,1744],{"class":73},[33,4617,106],{"class":65},[33,4619,3652],{"class":69},[33,4621,3655],{"class":73},[33,4623,4624,4626,4629],{"class":35,"line":479},[33,4625,3660],{"class":73},[33,4627,4628],{"class":115},"'scores'",[33,4630,4631],{"class":73},"],\n",[33,4633,4634,4636,4638,4640,4642,4644,4647,4649,4651,4653,4655,4657,4659,4661],{"class":35,"line":1490},[33,4635,3671],{"class":69},[33,4637,4057],{"class":73},[33,4639,147],{"class":65},[33,4641,1972],{"class":69},[33,4643,184],{"class":73},[33,4645,4646],{"class":115},"'\u002Fapi\u002Fscores'",[33,4648,2490],{"class":73},[33,4650,204],{"class":69},[33,4652,184],{"class":73},[33,4654,912],{"class":77},[33,4656,212],{"class":65},[33,4658,2501],{"class":73},[33,4660,291],{"class":69},[33,4662,2783],{"class":73},[33,4664,4665,4668,4671,4673,4675,4677],{"class":35,"line":1496},[33,4666,4667],{"class":73},"    refetchInterval: ",[33,4669,4670],{"class":93},"10",[33,4672,4365],{"class":65},[33,4674,4368],{"class":93},[33,4676,4552],{"class":73},[33,4678,4679],{"class":39},"\u002F\u002F poll every 10 seconds (live data)\n",[33,4681,4682,4684,4687,4690],{"class":35,"line":1502},[33,4683,4539],{"class":73},[33,4685,4686],{"class":93},"0",[33,4688,4689],{"class":73},",                ",[33,4691,4692],{"class":39},"\u002F\u002F always stale → always refetch on focus\n",[33,4694,4695],{"class":35,"line":2301},[33,4696,3778],{"class":73},[33,4698,4699,4701,4703,4706,4709,4711],{"class":35,"line":2328},[33,4700,463],{"class":65},[33,4702,394],{"class":73},[33,4704,4705],{"class":93},"Scoreboard",[33,4707,4708],{"class":69}," scores",[33,4710,106],{"class":65},[33,4712,1839],{"class":73},[33,4714,4715],{"class":35,"line":2333},[33,4716,482],{"class":73},[33,4718,4719],{"class":35,"line":2338},[33,4720,59],{"emptyLinePlaceholder":58},[33,4722,4723],{"class":35,"line":2847},[33,4724,4725],{"class":39},"\u002F\u002F OPTIMISTIC UPDATES: update the cache immediately, rollback on error\n",[33,4727,4728,4730,4733],{"class":35,"line":2874},[33,4729,66],{"class":65},[33,4731,4732],{"class":69}," useOptimisticUpdate",[33,4734,4605],{"class":73},[33,4736,4737,4739,4741,4743,4745],{"class":35,"line":2891},[33,4738,87],{"class":65},[33,4740,3949],{"class":93},[33,4742,1291],{"class":65},[33,4744,3954],{"class":69},[33,4746,294],{"class":73},[33,4748,4749],{"class":35,"line":2896},[33,4750,59],{"emptyLinePlaceholder":58},[33,4752,4753,4755,4757],{"class":35,"line":2911},[33,4754,463],{"class":65},[33,4756,3972],{"class":69},[33,4758,3655],{"class":73},[33,4760,4761,4763,4765,4768,4770,4772,4774,4776,4779,4782,4785,4788,4790,4792,4794],{"class":35,"line":2937},[33,4762,3979],{"class":69},[33,4764,3982],{"class":73},[33,4766,4767],{"class":77},"newTodo",[33,4769,388],{"class":73},[33,4771,147],{"class":65},[33,4773,1972],{"class":69},[33,4775,184],{"class":73},[33,4777,4778],{"class":115},"'\u002Fapi\u002Ftodos'",[33,4780,4781],{"class":73},", { method: ",[33,4783,4784],{"class":115},"'POST'",[33,4786,4787],{"class":73},", body: ",[33,4789,4036],{"class":93},[33,4791,248],{"class":73},[33,4793,4041],{"class":69},[33,4795,4796],{"class":73},"(newTodo) }),\n",[33,4798,4799,4802,4804,4806,4808,4810,4812,4814],{"class":35,"line":2942},[33,4800,4801],{"class":69},"    onMutate",[33,4803,3674],{"class":73},[33,4805,1879],{"class":65},[33,4807,223],{"class":73},[33,4809,4767],{"class":77},[33,4811,388],{"class":73},[33,4813,147],{"class":65},[33,4815,150],{"class":73},[33,4817,4818,4820,4823,4826,4828,4831],{"class":35,"line":2947},[33,4819,2076],{"class":65},[33,4821,4822],{"class":73}," queryClient.",[33,4824,4825],{"class":69},"cancelQueries",[33,4827,4082],{"class":73},[33,4829,4830],{"class":115},"'todos'",[33,4832,4833],{"class":73},"] })\n",[33,4835,4836,4838,4841,4843,4845,4848,4851,4853],{"class":35,"line":2962},[33,4837,1961],{"class":65},[33,4839,4840],{"class":93}," previousTodos",[33,4842,1291],{"class":65},[33,4844,4822],{"class":73},[33,4846,4847],{"class":69},"getQueryData",[33,4849,4850],{"class":73},"([",[33,4852,4830],{"class":115},[33,4854,4855],{"class":73},"])\n",[33,4857,4858,4860,4863,4865,4867,4870,4873,4875,4877,4879,4881,4884],{"class":35,"line":2967},[33,4859,4076],{"class":73},[33,4861,4862],{"class":69},"setQueryData",[33,4864,4850],{"class":73},[33,4866,4830],{"class":115},[33,4868,4869],{"class":73},"], (",[33,4871,4872],{"class":77},"old",[33,4874,388],{"class":73},[33,4876,147],{"class":65},[33,4878,90],{"class":73},[33,4880,3037],{"class":65},[33,4882,4883],{"class":73},"old, newTodo])  ",[33,4885,4886],{"class":39},"\u002F\u002F optimistic add\n",[33,4888,4889,4891,4894],{"class":35,"line":2972},[33,4890,2011],{"class":65},[33,4892,4893],{"class":73}," { previousTodos }  ",[33,4895,4896],{"class":39},"\u002F\u002F context for rollback\n",[33,4898,4899],{"class":35,"line":2978},[33,4900,3550],{"class":73},[33,4902,4903,4906,4908,4910,4912,4914,4916,4919,4921,4923],{"class":35,"line":2999},[33,4904,4905],{"class":69},"    onError",[33,4907,3982],{"class":73},[33,4909,1362],{"class":77},[33,4911,97],{"class":73},[33,4913,4767],{"class":77},[33,4915,97],{"class":73},[33,4917,4918],{"class":77},"context",[33,4920,388],{"class":73},[33,4922,147],{"class":65},[33,4924,150],{"class":73},[33,4926,4927,4929,4931,4933,4935,4938],{"class":35,"line":3024},[33,4928,4076],{"class":73},[33,4930,4862],{"class":69},[33,4932,4850],{"class":73},[33,4934,4830],{"class":115},[33,4936,4937],{"class":73},"], context.previousTodos)  ",[33,4939,4940],{"class":39},"\u002F\u002F rollback on error\n",[33,4942,4943],{"class":35,"line":3043},[33,4944,3550],{"class":73},[33,4946,4948,4951,4953,4955],{"class":35,"line":4947},51,[33,4949,4950],{"class":69},"    onSettled",[33,4952,4057],{"class":73},[33,4954,147],{"class":65},[33,4956,150],{"class":73},[33,4958,4960,4962,4964,4966,4968,4971],{"class":35,"line":4959},52,[33,4961,4076],{"class":73},[33,4963,4079],{"class":69},[33,4965,4082],{"class":73},[33,4967,4830],{"class":115},[33,4969,4970],{"class":73},"] })  ",[33,4972,4973],{"class":39},"\u002F\u002F always refetch after\n",[33,4975,4977],{"class":35,"line":4976},53,[33,4978,3550],{"class":73},[33,4980,4982],{"class":35,"line":4981},54,[33,4983,3778],{"class":73},[33,4985,4987],{"class":35,"line":4986},55,[33,4988,482],{"class":73},[14,4990,4992],{"id":4991},"swr-the-stale-while-revalidate-alternative","SWR: The Stale-While-Revalidate Alternative",[19,4994,4996],{"filename":4995,"language":22},"swr.js",[24,4997,4999],{"className":26,"code":4998,"language":22,"meta":28,"style":28},"\u002F\u002F SWR (from Vercel, named after HTTP's stale-while-revalidate caching strategy)\n\u002F\u002F solves largely the same problem set as React Query with a smaller API surface\n\u002F\u002F and a different philosophy: show cached (possibly stale) data immediately\n\u002F\u002F while silently refetching in the background.\n\nimport useSWR from 'swr'\n\nconst fetcher = (url) => fetch(url).then(r => {\n  if (!r.ok) throw new Error(`HTTP ${r.status}`)\n  return r.json()\n})\n\nfunction UserProfile({ userId }) {\n  const { data, error, isLoading, isValidating } = useSWR(\n    `\u002Fapi\u002Fusers\u002F${userId}`,  \u002F\u002F the key (also the URL passed to fetcher)\n    fetcher,\n    {\n      revalidateOnFocus: true,\n      refreshInterval: 0,       \u002F\u002F no polling by default\n      dedupingInterval: 2000,   \u002F\u002F dedupe requests within 2s\n    }\n  )\n\n  if (isLoading) return \u003CSpinner \u002F>\n  if (error) return \u003CErrorMessage error={error} \u002F>\n  return (\n    \u003Cdiv>\n      {isValidating && \u003CRefreshIndicator \u002F>}\n      \u003CProfileCard user={data} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F SWR mutation (manual revalidation):\nfunction EditProfileForm({ userId }) {\n  const { data, mutate } = useSWR(`\u002Fapi\u002Fusers\u002F${userId}`)\n\n  async function handleSubmit(e) {\n    e.preventDefault()\n    const updates = { name: e.target.name.value }\n    \u002F\u002F Optimistic update:\n    mutate({ ...data, ...updates }, false)  \u002F\u002F false = don't revalidate yet\n    await fetch(`\u002Fapi\u002Fusers\u002F${userId}`, { method: 'PATCH', body: JSON.stringify(updates) })\n    mutate()  \u002F\u002F revalidate (refetch the real data)\n  }\n}\n",[30,5000,5001,5006,5011,5016,5021,5025,5037,5041,5073,5104,5114,5118,5122,5134,5165,5179,5184,5189,5198,5211,5224,5228,5232,5236,5250,5269,5275,5283,5296,5308,5316,5320,5324,5328,5333,5345,5373,5377,5392,5400,5412,5417,5442,5472,5482,5486],{"__ignoreMap":28},[33,5002,5003],{"class":35,"line":36},[33,5004,5005],{"class":39},"\u002F\u002F SWR (from Vercel, named after HTTP's stale-while-revalidate caching strategy)\n",[33,5007,5008],{"class":35,"line":43},[33,5009,5010],{"class":39},"\u002F\u002F solves largely the same problem set as React Query with a smaller API surface\n",[33,5012,5013],{"class":35,"line":49},[33,5014,5015],{"class":39},"\u002F\u002F and a different philosophy: show cached (possibly stale) data immediately\n",[33,5017,5018],{"class":35,"line":55},[33,5019,5020],{"class":39},"\u002F\u002F while silently refetching in the background.\n",[33,5022,5023],{"class":35,"line":62},[33,5024,59],{"emptyLinePlaceholder":58},[33,5026,5027,5029,5032,5034],{"class":35,"line":84},[33,5028,3599],{"class":65},[33,5030,5031],{"class":73}," useSWR ",[33,5033,3605],{"class":65},[33,5035,5036],{"class":115}," 'swr'\n",[33,5038,5039],{"class":35,"line":133},[33,5040,59],{"emptyLinePlaceholder":58},[33,5042,5043,5045,5048,5050,5052,5054,5056,5058,5060,5063,5065,5067,5069,5071],{"class":35,"line":138},[33,5044,4333],{"class":65},[33,5046,5047],{"class":69}," fetcher",[33,5049,1291],{"class":65},[33,5051,223],{"class":73},[33,5053,1890],{"class":77},[33,5055,388],{"class":73},[33,5057,147],{"class":65},[33,5059,1972],{"class":69},[33,5061,5062],{"class":73},"(url).",[33,5064,204],{"class":69},[33,5066,184],{"class":73},[33,5068,912],{"class":77},[33,5070,212],{"class":65},[33,5072,150],{"class":73},[33,5074,5075,5077,5079,5081,5084,5086,5088,5090,5092,5094,5096,5098,5100,5102],{"class":35,"line":153},[33,5076,376],{"class":65},[33,5078,223],{"class":73},[33,5080,226],{"class":65},[33,5082,5083],{"class":73},"r.ok) ",[33,5085,232],{"class":65},[33,5087,235],{"class":65},[33,5089,238],{"class":69},[33,5091,184],{"class":73},[33,5093,243],{"class":115},[33,5095,912],{"class":73},[33,5097,248],{"class":115},[33,5099,251],{"class":73},[33,5101,192],{"class":115},[33,5103,195],{"class":73},[33,5105,5106,5108,5110,5112],{"class":35,"line":173},[33,5107,463],{"class":65},[33,5109,2501],{"class":73},[33,5111,291],{"class":69},[33,5113,294],{"class":73},[33,5115,5116],{"class":35,"line":178},[33,5117,4450],{"class":73},[33,5119,5120],{"class":35,"line":198},[33,5121,59],{"emptyLinePlaceholder":58},[33,5123,5124,5126,5128,5130,5132],{"class":35,"line":217},[33,5125,66],{"class":65},[33,5127,70],{"class":69},[33,5129,74],{"class":73},[33,5131,78],{"class":77},[33,5133,81],{"class":73},[33,5135,5136,5138,5140,5142,5144,5146,5148,5150,5152,5155,5157,5159,5162],{"class":35,"line":258},[33,5137,87],{"class":65},[33,5139,641],{"class":73},[33,5141,312],{"class":93},[33,5143,97],{"class":73},[33,5145,343],{"class":93},[33,5147,97],{"class":73},[33,5149,561],{"class":93},[33,5151,97],{"class":73},[33,5153,5154],{"class":93},"isValidating",[33,5156,1744],{"class":73},[33,5158,106],{"class":65},[33,5160,5161],{"class":69}," useSWR",[33,5163,5164],{"class":73},"(\n",[33,5166,5167,5170,5172,5174,5176],{"class":35,"line":264},[33,5168,5169],{"class":115},"    `\u002Fapi\u002Fusers\u002F${",[33,5171,78],{"class":73},[33,5173,192],{"class":115},[33,5175,4552],{"class":73},[33,5177,5178],{"class":39},"\u002F\u002F the key (also the URL passed to fetcher)\n",[33,5180,5181],{"class":35,"line":270},[33,5182,5183],{"class":73},"    fetcher,\n",[33,5185,5186],{"class":35,"line":276},[33,5187,5188],{"class":73},"    {\n",[33,5190,5191,5194,5196],{"class":35,"line":282},[33,5192,5193],{"class":73},"      revalidateOnFocus: ",[33,5195,577],{"class":93},[33,5197,3442],{"class":73},[33,5199,5200,5203,5205,5208],{"class":35,"line":297},[33,5201,5202],{"class":73},"      refreshInterval: ",[33,5204,4686],{"class":93},[33,5206,5207],{"class":73},",       ",[33,5209,5210],{"class":39},"\u002F\u002F no polling by default\n",[33,5212,5213,5216,5219,5221],{"class":35,"line":303},[33,5214,5215],{"class":73},"      dedupingInterval: ",[33,5217,5218],{"class":93},"2000",[33,5220,4433],{"class":73},[33,5222,5223],{"class":39},"\u002F\u002F dedupe requests within 2s\n",[33,5225,5226],{"class":35,"line":333},[33,5227,1633],{"class":73},[33,5229,5230],{"class":35,"line":362},[33,5231,3898],{"class":73},[33,5233,5234],{"class":35,"line":368},[33,5235,59],{"emptyLinePlaceholder":58},[33,5237,5238,5240,5242,5244,5246,5248],{"class":35,"line":373},[33,5239,376],{"class":65},[33,5241,694],{"class":73},[33,5243,391],{"class":65},[33,5245,394],{"class":73},[33,5247,397],{"class":93},[33,5249,400],{"class":73},[33,5251,5252,5254,5257,5259,5261,5263,5265,5267],{"class":35,"line":403},[33,5253,376],{"class":65},[33,5255,5256],{"class":73}," (error) ",[33,5258,391],{"class":65},[33,5260,394],{"class":73},[33,5262,421],{"class":93},[33,5264,424],{"class":69},[33,5266,106],{"class":65},[33,5268,3828],{"class":73},[33,5270,5271,5273],{"class":35,"line":432},[33,5272,463],{"class":65},[33,5274,3845],{"class":73},[33,5276,5277,5279,5281],{"class":35,"line":460},[33,5278,3850],{"class":73},[33,5280,3853],{"class":900},[33,5282,937],{"class":73},[33,5284,5285,5288,5290,5292,5294],{"class":35,"line":479},[33,5286,5287],{"class":73},"      {isValidating ",[33,5289,3863],{"class":65},[33,5291,394],{"class":73},[33,5293,3868],{"class":93},[33,5295,3871],{"class":73},[33,5297,5298,5300,5302,5304,5306],{"class":35,"line":1490},[33,5299,3876],{"class":73},[33,5301,468],{"class":93},[33,5303,471],{"class":69},[33,5305,106],{"class":65},[33,5307,1839],{"class":73},[33,5309,5310,5312,5314],{"class":35,"line":1496},[33,5311,3889],{"class":73},[33,5313,3853],{"class":900},[33,5315,937],{"class":73},[33,5317,5318],{"class":35,"line":1502},[33,5319,3898],{"class":73},[33,5321,5322],{"class":35,"line":2301},[33,5323,482],{"class":73},[33,5325,5326],{"class":35,"line":2328},[33,5327,59],{"emptyLinePlaceholder":58},[33,5329,5330],{"class":35,"line":2333},[33,5331,5332],{"class":39},"\u002F\u002F SWR mutation (manual revalidation):\n",[33,5334,5335,5337,5339,5341,5343],{"class":35,"line":2338},[33,5336,66],{"class":65},[33,5338,3936],{"class":69},[33,5340,74],{"class":73},[33,5342,78],{"class":77},[33,5344,81],{"class":73},[33,5346,5347,5349,5351,5353,5355,5357,5359,5361,5363,5365,5367,5369,5371],{"class":35,"line":2847},[33,5348,87],{"class":65},[33,5350,641],{"class":73},[33,5352,312],{"class":93},[33,5354,97],{"class":73},[33,5356,4132],{"class":93},[33,5358,1744],{"class":73},[33,5360,106],{"class":65},[33,5362,5161],{"class":69},[33,5364,184],{"class":73},[33,5366,187],{"class":115},[33,5368,78],{"class":73},[33,5370,192],{"class":115},[33,5372,195],{"class":73},[33,5374,5375],{"class":35,"line":2874},[33,5376,59],{"emptyLinePlaceholder":58},[33,5378,5379,5382,5384,5386,5388,5390],{"class":35,"line":2891},[33,5380,5381],{"class":65},"  async",[33,5383,1882],{"class":65},[33,5385,4107],{"class":69},[33,5387,184],{"class":73},[33,5389,4112],{"class":77},[33,5391,1560],{"class":73},[33,5393,5394,5396,5398],{"class":35,"line":2896},[33,5395,4119],{"class":73},[33,5397,4122],{"class":69},[33,5399,294],{"class":73},[33,5401,5402,5404,5407,5409],{"class":35,"line":2911},[33,5403,1285],{"class":65},[33,5405,5406],{"class":93}," updates",[33,5408,1291],{"class":65},[33,5410,5411],{"class":73}," { name: e.target.name.value }\n",[33,5413,5414],{"class":35,"line":2937},[33,5415,5416],{"class":39},"    \u002F\u002F Optimistic update:\n",[33,5418,5419,5422,5424,5426,5429,5431,5434,5436,5439],{"class":35,"line":2942},[33,5420,5421],{"class":69},"    mutate",[33,5423,74],{"class":73},[33,5425,3037],{"class":65},[33,5427,5428],{"class":73},"data, ",[33,5430,3037],{"class":65},[33,5432,5433],{"class":73},"updates }, ",[33,5435,653],{"class":93},[33,5437,5438],{"class":73},")  ",[33,5440,5441],{"class":39},"\u002F\u002F false = don't revalidate yet\n",[33,5443,5444,5447,5449,5451,5453,5455,5457,5459,5461,5463,5465,5467,5469],{"class":35,"line":2947},[33,5445,5446],{"class":65},"    await",[33,5448,1972],{"class":69},[33,5450,184],{"class":73},[33,5452,187],{"class":115},[33,5454,78],{"class":73},[33,5456,192],{"class":115},[33,5458,4781],{"class":73},[33,5460,4010],{"class":115},[33,5462,4787],{"class":73},[33,5464,4036],{"class":93},[33,5466,248],{"class":73},[33,5468,4041],{"class":69},[33,5470,5471],{"class":73},"(updates) })\n",[33,5473,5474,5476,5479],{"class":35,"line":2962},[33,5475,5421],{"class":69},[33,5477,5478],{"class":73},"()  ",[33,5480,5481],{"class":39},"\u002F\u002F revalidate (refetch the real data)\n",[33,5483,5484],{"class":35,"line":2967},[33,5485,2114],{"class":73},[33,5487,5488],{"class":35,"line":2972},[33,5489,482],{"class":73},[484,5491,5493],{"id":5492},"react-query-vs-swr-comparison","React Query vs SWR Comparison",[19,5495,5497],{"filename":5496,"language":22},"rq_vs_swr.js",[24,5498,5500],{"className":26,"code":5499,"language":22,"meta":28,"style":28},"\u002F\u002F Feature comparison:\n\n\u002F\u002F | Feature              | React Query              | SWR                     |\n\u002F\u002F |----------------------|--------------------------|-------------------------|\n\u002F\u002F | API surface          | Larger, more explicit    | Smaller, more implicit  |\n\u002F\u002F | DevTools             | Excellent built-in       | Limited                 |\n\u002F\u002F | Mutations            | First-class useMutation  | Manual via mutate()     |\n\u002F\u002F | Optimistic updates   | Built-in onMutate\u002FonError| Manual                  |\n\u002F\u002F | Infinite queries     | useInfiniteQuery         | useSWRInfinite          |\n\u002F\u002F | Prefetching          | queryClient.prefetchQuery| mutate with data        |\n\u002F\u002F | SSR support          | hydrateQueryClient       | useSWR + fallback       |\n\u002F\u002F | Framework agnostic   | Yes (TanStack Query)     | Yes                     |\n\u002F\u002F | Bundle size          | ~13KB                    | ~5KB                    |\n\u002F\u002F | Philosophy           | Explicit status machine  | Show stale, revalidate  |\n\n\u002F\u002F WHEN TO CHOOSE REACT QUERY:\n\u002F\u002F - Complex caching\u002Fmutation needs (optimistic updates, prefetch, invalidation)\n\u002F\u002F - Need rich DevTools for debugging query state\n\u002F\u002F - Large app with many interdependent queries\n\u002F\u002F - Need fine-grained control over retry, refetch, gc strategies\n\n\u002F\u002F WHEN TO CHOOSE SWR:\n\u002F\u002F - Smaller app, simpler needs\n\u002F\u002F - Want the smallest possible bundle\n\u002F\u002F - Prefer implicit \"just show data and refetch\" over explicit status management\n\u002F\u002F - Mostly read-heavy with few mutations\n",[30,5501,5502,5507,5511,5516,5521,5526,5531,5536,5541,5546,5551,5556,5561,5566,5571,5575,5580,5585,5590,5595,5600,5604,5609,5614,5619,5624],{"__ignoreMap":28},[33,5503,5504],{"class":35,"line":36},[33,5505,5506],{"class":39},"\u002F\u002F Feature comparison:\n",[33,5508,5509],{"class":35,"line":43},[33,5510,59],{"emptyLinePlaceholder":58},[33,5512,5513],{"class":35,"line":49},[33,5514,5515],{"class":39},"\u002F\u002F | Feature              | React Query              | SWR                     |\n",[33,5517,5518],{"class":35,"line":55},[33,5519,5520],{"class":39},"\u002F\u002F |----------------------|--------------------------|-------------------------|\n",[33,5522,5523],{"class":35,"line":62},[33,5524,5525],{"class":39},"\u002F\u002F | API surface          | Larger, more explicit    | Smaller, more implicit  |\n",[33,5527,5528],{"class":35,"line":84},[33,5529,5530],{"class":39},"\u002F\u002F | DevTools             | Excellent built-in       | Limited                 |\n",[33,5532,5533],{"class":35,"line":133},[33,5534,5535],{"class":39},"\u002F\u002F | Mutations            | First-class useMutation  | Manual via mutate()     |\n",[33,5537,5538],{"class":35,"line":138},[33,5539,5540],{"class":39},"\u002F\u002F | Optimistic updates   | Built-in onMutate\u002FonError| Manual                  |\n",[33,5542,5543],{"class":35,"line":153},[33,5544,5545],{"class":39},"\u002F\u002F | Infinite queries     | useInfiniteQuery         | useSWRInfinite          |\n",[33,5547,5548],{"class":35,"line":173},[33,5549,5550],{"class":39},"\u002F\u002F | Prefetching          | queryClient.prefetchQuery| mutate with data        |\n",[33,5552,5553],{"class":35,"line":178},[33,5554,5555],{"class":39},"\u002F\u002F | SSR support          | hydrateQueryClient       | useSWR + fallback       |\n",[33,5557,5558],{"class":35,"line":198},[33,5559,5560],{"class":39},"\u002F\u002F | Framework agnostic   | Yes (TanStack Query)     | Yes                     |\n",[33,5562,5563],{"class":35,"line":217},[33,5564,5565],{"class":39},"\u002F\u002F | Bundle size          | ~13KB                    | ~5KB                    |\n",[33,5567,5568],{"class":35,"line":258},[33,5569,5570],{"class":39},"\u002F\u002F | Philosophy           | Explicit status machine  | Show stale, revalidate  |\n",[33,5572,5573],{"class":35,"line":264},[33,5574,59],{"emptyLinePlaceholder":58},[33,5576,5577],{"class":35,"line":270},[33,5578,5579],{"class":39},"\u002F\u002F WHEN TO CHOOSE REACT QUERY:\n",[33,5581,5582],{"class":35,"line":276},[33,5583,5584],{"class":39},"\u002F\u002F - Complex caching\u002Fmutation needs (optimistic updates, prefetch, invalidation)\n",[33,5586,5587],{"class":35,"line":282},[33,5588,5589],{"class":39},"\u002F\u002F - Need rich DevTools for debugging query state\n",[33,5591,5592],{"class":35,"line":297},[33,5593,5594],{"class":39},"\u002F\u002F - Large app with many interdependent queries\n",[33,5596,5597],{"class":35,"line":303},[33,5598,5599],{"class":39},"\u002F\u002F - Need fine-grained control over retry, refetch, gc strategies\n",[33,5601,5602],{"class":35,"line":333},[33,5603,59],{"emptyLinePlaceholder":58},[33,5605,5606],{"class":35,"line":362},[33,5607,5608],{"class":39},"\u002F\u002F WHEN TO CHOOSE SWR:\n",[33,5610,5611],{"class":35,"line":368},[33,5612,5613],{"class":39},"\u002F\u002F - Smaller app, simpler needs\n",[33,5615,5616],{"class":35,"line":373},[33,5617,5618],{"class":39},"\u002F\u002F - Want the smallest possible bundle\n",[33,5620,5621],{"class":35,"line":403},[33,5622,5623],{"class":39},"\u002F\u002F - Prefer implicit \"just show data and refetch\" over explicit status management\n",[33,5625,5626],{"class":35,"line":432},[33,5627,5628],{"class":39},"\u002F\u002F - Mostly read-heavy with few mutations\n",[14,5630,5632,5634,5635,5637],{"id":5631},"isloading-vs-isfetching-a-critical-distinction",[30,5633,561],{}," vs ",[30,5636,3645],{},": A Critical Distinction",[19,5639,5641],{"filename":5640,"language":22},"loading_vs_fetching.js",[24,5642,5644],{"className":26,"code":5643,"language":22,"meta":28,"style":28},"\u002F\u002F When migrating from hand-rolled fetching to React Query\u002FSWR, a common mistake\n\u002F\u002F is conflating these two states:\n\n\u002F\u002F isLoading (React Query) \u002F isLoading (SWR):\n\u002F\u002F   True when there is NO data yet at all — the first fetch is in progress.\n\u002F\u002F   After the first successful fetch, isLoading is forever false (even during\n\u002F\u002F   background refetches) because cached data exists.\n\n\u002F\u002F isFetching (React Query) \u002F isValidating (SWR):\n\u002F\u002F   True whenever a request is in progress, INCLUDING background refetches when\n\u002F\u002F   cached data is already shown. False when no request is active.\n\n\u002F\u002F PRACTICAL IMPACT:\nfunction ProductList() {\n  const { data, isLoading, isFetching } = useQuery({\n    queryKey: ['products'],\n    queryFn: fetchProducts,\n  })\n\n  \u002F\u002F WRONG: using isLoading for the spinner — after the first load, background\n  \u002F\u002F refetches (on focus, on reconnect) won't show ANY indicator because\n  \u002F\u002F isLoading is false (data already exists from cache).\n  if (isLoading) return \u003CFullScreenSpinner \u002F>\n\n  \u002F\u002F CORRECT: use isLoading for the initial full-screen state, isFetching for\n  \u002F\u002F a subtle background-refresh indicator on top of existing data.\n  if (isLoading) return \u003CFullScreenSpinner \u002F>\n  return (\n    \u003Cdiv>\n      {isFetching && \u003CRefreshBar \u002F>}  {\u002F* subtle top-bar shimmer, not a full spinner *\u002F}\n      \u003CProductGrid products={data} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,5645,5646,5651,5656,5660,5665,5670,5675,5680,5684,5689,5694,5699,5703,5708,5716,5740,5748,5753,5757,5761,5766,5771,5776,5791,5795,5800,5805,5819,5825,5833,5852,5864,5872,5876],{"__ignoreMap":28},[33,5647,5648],{"class":35,"line":36},[33,5649,5650],{"class":39},"\u002F\u002F When migrating from hand-rolled fetching to React Query\u002FSWR, a common mistake\n",[33,5652,5653],{"class":35,"line":43},[33,5654,5655],{"class":39},"\u002F\u002F is conflating these two states:\n",[33,5657,5658],{"class":35,"line":49},[33,5659,59],{"emptyLinePlaceholder":58},[33,5661,5662],{"class":35,"line":55},[33,5663,5664],{"class":39},"\u002F\u002F isLoading (React Query) \u002F isLoading (SWR):\n",[33,5666,5667],{"class":35,"line":62},[33,5668,5669],{"class":39},"\u002F\u002F   True when there is NO data yet at all — the first fetch is in progress.\n",[33,5671,5672],{"class":35,"line":84},[33,5673,5674],{"class":39},"\u002F\u002F   After the first successful fetch, isLoading is forever false (even during\n",[33,5676,5677],{"class":35,"line":133},[33,5678,5679],{"class":39},"\u002F\u002F   background refetches) because cached data exists.\n",[33,5681,5682],{"class":35,"line":138},[33,5683,59],{"emptyLinePlaceholder":58},[33,5685,5686],{"class":35,"line":153},[33,5687,5688],{"class":39},"\u002F\u002F isFetching (React Query) \u002F isValidating (SWR):\n",[33,5690,5691],{"class":35,"line":173},[33,5692,5693],{"class":39},"\u002F\u002F   True whenever a request is in progress, INCLUDING background refetches when\n",[33,5695,5696],{"class":35,"line":178},[33,5697,5698],{"class":39},"\u002F\u002F   cached data is already shown. False when no request is active.\n",[33,5700,5701],{"class":35,"line":198},[33,5702,59],{"emptyLinePlaceholder":58},[33,5704,5705],{"class":35,"line":217},[33,5706,5707],{"class":39},"\u002F\u002F PRACTICAL IMPACT:\n",[33,5709,5710,5712,5714],{"class":35,"line":258},[33,5711,66],{"class":65},[33,5713,4466],{"class":69},[33,5715,4605],{"class":73},[33,5717,5718,5720,5722,5724,5726,5728,5730,5732,5734,5736,5738],{"class":35,"line":264},[33,5719,87],{"class":65},[33,5721,641],{"class":73},[33,5723,312],{"class":93},[33,5725,97],{"class":73},[33,5727,561],{"class":93},[33,5729,97],{"class":73},[33,5731,3645],{"class":93},[33,5733,1744],{"class":73},[33,5735,106],{"class":65},[33,5737,3652],{"class":69},[33,5739,3655],{"class":73},[33,5741,5742,5744,5746],{"class":35,"line":270},[33,5743,3660],{"class":73},[33,5745,4496],{"class":115},[33,5747,4631],{"class":73},[33,5749,5750],{"class":35,"line":276},[33,5751,5752],{"class":73},"    queryFn: fetchProducts,\n",[33,5754,5755],{"class":35,"line":282},[33,5756,3778],{"class":73},[33,5758,5759],{"class":35,"line":297},[33,5760,59],{"emptyLinePlaceholder":58},[33,5762,5763],{"class":35,"line":303},[33,5764,5765],{"class":39},"  \u002F\u002F WRONG: using isLoading for the spinner — after the first load, background\n",[33,5767,5768],{"class":35,"line":333},[33,5769,5770],{"class":39},"  \u002F\u002F refetches (on focus, on reconnect) won't show ANY indicator because\n",[33,5772,5773],{"class":35,"line":362},[33,5774,5775],{"class":39},"  \u002F\u002F isLoading is false (data already exists from cache).\n",[33,5777,5778,5780,5782,5784,5786,5789],{"class":35,"line":368},[33,5779,376],{"class":65},[33,5781,694],{"class":73},[33,5783,391],{"class":65},[33,5785,394],{"class":73},[33,5787,5788],{"class":93},"FullScreenSpinner",[33,5790,400],{"class":73},[33,5792,5793],{"class":35,"line":373},[33,5794,59],{"emptyLinePlaceholder":58},[33,5796,5797],{"class":35,"line":403},[33,5798,5799],{"class":39},"  \u002F\u002F CORRECT: use isLoading for the initial full-screen state, isFetching for\n",[33,5801,5802],{"class":35,"line":432},[33,5803,5804],{"class":39},"  \u002F\u002F a subtle background-refresh indicator on top of existing data.\n",[33,5806,5807,5809,5811,5813,5815,5817],{"class":35,"line":460},[33,5808,376],{"class":65},[33,5810,694],{"class":73},[33,5812,391],{"class":65},[33,5814,394],{"class":73},[33,5816,5788],{"class":93},[33,5818,400],{"class":73},[33,5820,5821,5823],{"class":35,"line":479},[33,5822,463],{"class":65},[33,5824,3845],{"class":73},[33,5826,5827,5829,5831],{"class":35,"line":1490},[33,5828,3850],{"class":73},[33,5830,3853],{"class":900},[33,5832,937],{"class":73},[33,5834,5835,5837,5839,5841,5844,5847,5850],{"class":35,"line":1496},[33,5836,3860],{"class":73},[33,5838,3863],{"class":65},[33,5840,394],{"class":73},[33,5842,5843],{"class":93},"RefreshBar",[33,5845,5846],{"class":73}," \u002F>}  {",[33,5848,5849],{"class":39},"\u002F* subtle top-bar shimmer, not a full spinner *\u002F",[33,5851,482],{"class":73},[33,5853,5854,5856,5858,5860,5862],{"class":35,"line":1502},[33,5855,3876],{"class":73},[33,5857,4580],{"class":93},[33,5859,4583],{"class":69},[33,5861,106],{"class":65},[33,5863,1839],{"class":73},[33,5865,5866,5868,5870],{"class":35,"line":2301},[33,5867,3889],{"class":73},[33,5869,3853],{"class":900},[33,5871,937],{"class":73},[33,5873,5874],{"class":35,"line":2328},[33,5875,3898],{"class":73},[33,5877,5878],{"class":35,"line":2333},[33,5879,482],{"class":73},[14,5881,5883],{"id":5882},"suspense-for-data-fetching-react-18","Suspense for Data Fetching (React 18+)",[19,5885,5887],{"filename":5886,"language":22},"suspense.js",[24,5888,5890],{"className":26,"code":5889,"language":22,"meta":28,"style":28},"\u002F\u002F React 18 introduced Suspense for data fetching: a component can \"suspend\"\n\u002F\u002F (throw a promise) while waiting for data, and a parent \u003CSuspense> boundary\n\u002F\u002F catches it and shows a fallback until the promise resolves.\n\nimport { Suspense } from 'react'\n\n\u002F\u002F With React Query's experimental Suspense mode:\nfunction UserProfile({ userId }) {\n  \u002F\u002F useSuspenseQuery throws a promise if data isn't ready — the nearest\n  \u002F\u002F \u003CSuspense> boundary catches it and shows its fallback.\n  const { data } = useSuspenseQuery({\n    queryKey: ['user', userId],\n    queryFn: () => fetch(`\u002Fapi\u002Fusers\u002F${userId}`).then(r => r.json()),\n  })\n  \u002F\u002F `data` is ALWAYS defined here — no null check needed. If data wasn't ready,\n  \u002F\u002F the component suspended above this line and never reached this point.\n  return \u003CProfileCard user={data} \u002F>\n}\n\n\u002F\u002F The parent handles the loading state declaratively:\nfunction Page({ userId }) {\n  return (\n    \u003CSuspense fallback={\u003CSpinner \u002F>}>\n      \u003CUserProfile userId={userId} \u002F>\n    \u003C\u002FSuspense>\n  )\n}\n\n\u002F\u002F NESTED SUSPENSE: each boundary can have its own fallback, so parts of the\n\u002F\u002F page load independently without blocking each other.\nfunction Dashboard({ userId }) {\n  return (\n    \u003Cdiv>\n      \u003CSuspense fallback={\u003CHeaderSkeleton \u002F>}>\n        \u003CHeader userId={userId} \u002F>\n      \u003C\u002FSuspense>\n      \u003CSuspense fallback={\u003CFeedSkeleton \u002F>}>\n        \u003CActivityFeed userId={userId} \u002F>\n      \u003C\u002FSuspense>\n      \u003CSuspense fallback={\u003CChartSkeleton \u002F>}>\n        \u003CAnalyticsChart userId={userId} \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002Fdiv>\n  )\n  \u002F\u002F Header, Feed, and Chart all fetch in parallel and render independently as\n  \u002F\u002F their data arrives — no single slow request blocks the others.\n}\n",[30,5891,5892,5897,5902,5907,5911,5923,5927,5932,5944,5949,5954,5971,5979,6013,6017,6022,6027,6041,6045,6049,6054,6067,6073,6093,6108,6116,6120,6124,6128,6133,6138,6151,6157,6165,6182,6196,6204,6221,6234,6242,6259,6272,6280,6288,6292,6297,6302],{"__ignoreMap":28},[33,5893,5894],{"class":35,"line":36},[33,5895,5896],{"class":39},"\u002F\u002F React 18 introduced Suspense for data fetching: a component can \"suspend\"\n",[33,5898,5899],{"class":35,"line":43},[33,5900,5901],{"class":39},"\u002F\u002F (throw a promise) while waiting for data, and a parent \u003CSuspense> boundary\n",[33,5903,5904],{"class":35,"line":49},[33,5905,5906],{"class":39},"\u002F\u002F catches it and shows a fallback until the promise resolves.\n",[33,5908,5909],{"class":35,"line":55},[33,5910,59],{"emptyLinePlaceholder":58},[33,5912,5913,5915,5918,5920],{"class":35,"line":62},[33,5914,3599],{"class":65},[33,5916,5917],{"class":73}," { Suspense } ",[33,5919,3605],{"class":65},[33,5921,5922],{"class":115}," 'react'\n",[33,5924,5925],{"class":35,"line":84},[33,5926,59],{"emptyLinePlaceholder":58},[33,5928,5929],{"class":35,"line":133},[33,5930,5931],{"class":39},"\u002F\u002F With React Query's experimental Suspense mode:\n",[33,5933,5934,5936,5938,5940,5942],{"class":35,"line":138},[33,5935,66],{"class":65},[33,5937,70],{"class":69},[33,5939,74],{"class":73},[33,5941,78],{"class":77},[33,5943,81],{"class":73},[33,5945,5946],{"class":35,"line":153},[33,5947,5948],{"class":39},"  \u002F\u002F useSuspenseQuery throws a promise if data isn't ready — the nearest\n",[33,5950,5951],{"class":35,"line":173},[33,5952,5953],{"class":39},"  \u002F\u002F \u003CSuspense> boundary catches it and shows its fallback.\n",[33,5955,5956,5958,5960,5962,5964,5966,5969],{"class":35,"line":178},[33,5957,87],{"class":65},[33,5959,641],{"class":73},[33,5961,312],{"class":93},[33,5963,1744],{"class":73},[33,5965,106],{"class":65},[33,5967,5968],{"class":69}," useSuspenseQuery",[33,5970,3655],{"class":73},[33,5972,5973,5975,5977],{"class":35,"line":198},[33,5974,3660],{"class":73},[33,5976,3663],{"class":115},[33,5978,3666],{"class":73},[33,5980,5981,5983,5985,5987,5989,5991,5993,5995,5997,5999,6001,6003,6005,6007,6009,6011],{"class":35,"line":217},[33,5982,3671],{"class":69},[33,5984,4057],{"class":73},[33,5986,147],{"class":65},[33,5988,1972],{"class":69},[33,5990,184],{"class":73},[33,5992,187],{"class":115},[33,5994,78],{"class":73},[33,5996,192],{"class":115},[33,5998,2490],{"class":73},[33,6000,204],{"class":69},[33,6002,184],{"class":73},[33,6004,912],{"class":77},[33,6006,212],{"class":65},[33,6008,2501],{"class":73},[33,6010,291],{"class":69},[33,6012,2783],{"class":73},[33,6014,6015],{"class":35,"line":258},[33,6016,3778],{"class":73},[33,6018,6019],{"class":35,"line":264},[33,6020,6021],{"class":39},"  \u002F\u002F `data` is ALWAYS defined here — no null check needed. If data wasn't ready,\n",[33,6023,6024],{"class":35,"line":270},[33,6025,6026],{"class":39},"  \u002F\u002F the component suspended above this line and never reached this point.\n",[33,6028,6029,6031,6033,6035,6037,6039],{"class":35,"line":276},[33,6030,463],{"class":65},[33,6032,394],{"class":73},[33,6034,468],{"class":93},[33,6036,471],{"class":69},[33,6038,106],{"class":65},[33,6040,1839],{"class":73},[33,6042,6043],{"class":35,"line":282},[33,6044,482],{"class":73},[33,6046,6047],{"class":35,"line":297},[33,6048,59],{"emptyLinePlaceholder":58},[33,6050,6051],{"class":35,"line":303},[33,6052,6053],{"class":39},"\u002F\u002F The parent handles the loading state declaratively:\n",[33,6055,6056,6058,6061,6063,6065],{"class":35,"line":333},[33,6057,66],{"class":65},[33,6059,6060],{"class":69}," Page",[33,6062,74],{"class":73},[33,6064,78],{"class":77},[33,6066,81],{"class":73},[33,6068,6069,6071],{"class":35,"line":362},[33,6070,463],{"class":65},[33,6072,3845],{"class":73},[33,6074,6075,6077,6080,6083,6085,6088,6090],{"class":35,"line":368},[33,6076,3850],{"class":73},[33,6078,6079],{"class":93},"Suspense",[33,6081,6082],{"class":69}," fallback",[33,6084,106],{"class":65},[33,6086,6087],{"class":73},"{\u003C",[33,6089,397],{"class":93},[33,6091,6092],{"class":73}," \u002F>}>\n",[33,6094,6095,6097,6100,6103,6105],{"class":35,"line":373},[33,6096,3876],{"class":73},[33,6098,6099],{"class":93},"UserProfile",[33,6101,6102],{"class":69}," userId",[33,6104,106],{"class":65},[33,6106,6107],{"class":73},"{userId} \u002F>\n",[33,6109,6110,6112,6114],{"class":35,"line":403},[33,6111,3889],{"class":73},[33,6113,6079],{"class":93},[33,6115,937],{"class":73},[33,6117,6118],{"class":35,"line":432},[33,6119,3898],{"class":73},[33,6121,6122],{"class":35,"line":460},[33,6123,482],{"class":73},[33,6125,6126],{"class":35,"line":479},[33,6127,59],{"emptyLinePlaceholder":58},[33,6129,6130],{"class":35,"line":1490},[33,6131,6132],{"class":39},"\u002F\u002F NESTED SUSPENSE: each boundary can have its own fallback, so parts of the\n",[33,6134,6135],{"class":35,"line":1496},[33,6136,6137],{"class":39},"\u002F\u002F page load independently without blocking each other.\n",[33,6139,6140,6142,6145,6147,6149],{"class":35,"line":1502},[33,6141,66],{"class":65},[33,6143,6144],{"class":69}," Dashboard",[33,6146,74],{"class":73},[33,6148,78],{"class":77},[33,6150,81],{"class":73},[33,6152,6153,6155],{"class":35,"line":2301},[33,6154,463],{"class":65},[33,6156,3845],{"class":73},[33,6158,6159,6161,6163],{"class":35,"line":2328},[33,6160,3850],{"class":73},[33,6162,3853],{"class":900},[33,6164,937],{"class":73},[33,6166,6167,6169,6171,6173,6175,6177,6180],{"class":35,"line":2333},[33,6168,3876],{"class":73},[33,6170,6079],{"class":93},[33,6172,6082],{"class":69},[33,6174,106],{"class":65},[33,6176,6087],{"class":73},[33,6178,6179],{"class":93},"HeaderSkeleton",[33,6181,6092],{"class":73},[33,6183,6184,6187,6190,6192,6194],{"class":35,"line":2338},[33,6185,6186],{"class":73},"        \u003C",[33,6188,6189],{"class":93},"Header",[33,6191,6102],{"class":69},[33,6193,106],{"class":65},[33,6195,6107],{"class":73},[33,6197,6198,6200,6202],{"class":35,"line":2847},[33,6199,4236],{"class":73},[33,6201,6079],{"class":93},[33,6203,937],{"class":73},[33,6205,6206,6208,6210,6212,6214,6216,6219],{"class":35,"line":2874},[33,6207,3876],{"class":73},[33,6209,6079],{"class":93},[33,6211,6082],{"class":69},[33,6213,106],{"class":65},[33,6215,6087],{"class":73},[33,6217,6218],{"class":93},"FeedSkeleton",[33,6220,6092],{"class":73},[33,6222,6223,6225,6228,6230,6232],{"class":35,"line":2891},[33,6224,6186],{"class":73},[33,6226,6227],{"class":93},"ActivityFeed",[33,6229,6102],{"class":69},[33,6231,106],{"class":65},[33,6233,6107],{"class":73},[33,6235,6236,6238,6240],{"class":35,"line":2896},[33,6237,4236],{"class":73},[33,6239,6079],{"class":93},[33,6241,937],{"class":73},[33,6243,6244,6246,6248,6250,6252,6254,6257],{"class":35,"line":2911},[33,6245,3876],{"class":73},[33,6247,6079],{"class":93},[33,6249,6082],{"class":69},[33,6251,106],{"class":65},[33,6253,6087],{"class":73},[33,6255,6256],{"class":93},"ChartSkeleton",[33,6258,6092],{"class":73},[33,6260,6261,6263,6266,6268,6270],{"class":35,"line":2937},[33,6262,6186],{"class":73},[33,6264,6265],{"class":93},"AnalyticsChart",[33,6267,6102],{"class":69},[33,6269,106],{"class":65},[33,6271,6107],{"class":73},[33,6273,6274,6276,6278],{"class":35,"line":2942},[33,6275,4236],{"class":73},[33,6277,6079],{"class":93},[33,6279,937],{"class":73},[33,6281,6282,6284,6286],{"class":35,"line":2947},[33,6283,3889],{"class":73},[33,6285,3853],{"class":900},[33,6287,937],{"class":73},[33,6289,6290],{"class":35,"line":2962},[33,6291,3898],{"class":73},[33,6293,6294],{"class":35,"line":2967},[33,6295,6296],{"class":39},"  \u002F\u002F Header, Feed, and Chart all fetch in parallel and render independently as\n",[33,6298,6299],{"class":35,"line":2972},[33,6300,6301],{"class":39},"  \u002F\u002F their data arrives — no single slow request blocks the others.\n",[33,6303,6304],{"class":35,"line":2978},[33,6305,482],{"class":73},[484,6307,6309],{"id":6308},"suspense-error-boundaries","Suspense + Error Boundaries",[19,6311,6313],{"filename":6312,"language":22},"suspense_errors.js",[24,6314,6316],{"className":26,"code":6315,"language":22,"meta":28,"style":28},"\u002F\u002F Suspense catches suspended PROMISES, not ERRORS. A fetch error during a\n\u002F\u002F suspended render is thrown synchronously after the promise rejects — that\n\u002F\u002F error needs an ERROR BOUNDARY (chapter 16), not a Suspense boundary.\n\nimport { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\n\nfunction Page({ userId }) {\n  return (\n    \u003CErrorBoundary FallbackComponent={ErrorFallback}>\n      \u003CSuspense fallback={\u003CSpinner \u002F>}>\n        \u003CUserProfile userId={userId} \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002FErrorBoundary>\n  )\n  \u002F\u002F The ErrorBoundary wraps the Suspense boundary — if the suspended fetch\n  \u002F\u002F rejects, the error propagates to the ErrorBoundary, which shows its fallback.\n}\n",[30,6317,6318,6323,6328,6333,6337,6347,6359,6363,6375,6381,6396,6412,6424,6432,6440,6444,6449,6454],{"__ignoreMap":28},[33,6319,6320],{"class":35,"line":36},[33,6321,6322],{"class":39},"\u002F\u002F Suspense catches suspended PROMISES, not ERRORS. A fetch error during a\n",[33,6324,6325],{"class":35,"line":43},[33,6326,6327],{"class":39},"\u002F\u002F suspended render is thrown synchronously after the promise rejects — that\n",[33,6329,6330],{"class":35,"line":49},[33,6331,6332],{"class":39},"\u002F\u002F error needs an ERROR BOUNDARY (chapter 16), not a Suspense boundary.\n",[33,6334,6335],{"class":35,"line":55},[33,6336,59],{"emptyLinePlaceholder":58},[33,6338,6339,6341,6343,6345],{"class":35,"line":62},[33,6340,3599],{"class":65},[33,6342,5917],{"class":73},[33,6344,3605],{"class":65},[33,6346,5922],{"class":115},[33,6348,6349,6351,6354,6356],{"class":35,"line":84},[33,6350,3599],{"class":65},[33,6352,6353],{"class":73}," { ErrorBoundary } ",[33,6355,3605],{"class":65},[33,6357,6358],{"class":115}," 'react-error-boundary'\n",[33,6360,6361],{"class":35,"line":133},[33,6362,59],{"emptyLinePlaceholder":58},[33,6364,6365,6367,6369,6371,6373],{"class":35,"line":138},[33,6366,66],{"class":65},[33,6368,6060],{"class":69},[33,6370,74],{"class":73},[33,6372,78],{"class":77},[33,6374,81],{"class":73},[33,6376,6377,6379],{"class":35,"line":153},[33,6378,463],{"class":65},[33,6380,3845],{"class":73},[33,6382,6383,6385,6388,6391,6393],{"class":35,"line":173},[33,6384,3850],{"class":73},[33,6386,6387],{"class":93},"ErrorBoundary",[33,6389,6390],{"class":69}," FallbackComponent",[33,6392,106],{"class":65},[33,6394,6395],{"class":73},"{ErrorFallback}>\n",[33,6397,6398,6400,6402,6404,6406,6408,6410],{"class":35,"line":178},[33,6399,3876],{"class":73},[33,6401,6079],{"class":93},[33,6403,6082],{"class":69},[33,6405,106],{"class":65},[33,6407,6087],{"class":73},[33,6409,397],{"class":93},[33,6411,6092],{"class":73},[33,6413,6414,6416,6418,6420,6422],{"class":35,"line":198},[33,6415,6186],{"class":73},[33,6417,6099],{"class":93},[33,6419,6102],{"class":69},[33,6421,106],{"class":65},[33,6423,6107],{"class":73},[33,6425,6426,6428,6430],{"class":35,"line":217},[33,6427,4236],{"class":73},[33,6429,6079],{"class":93},[33,6431,937],{"class":73},[33,6433,6434,6436,6438],{"class":35,"line":258},[33,6435,3889],{"class":73},[33,6437,6387],{"class":93},[33,6439,937],{"class":73},[33,6441,6442],{"class":35,"line":264},[33,6443,3898],{"class":73},[33,6445,6446],{"class":35,"line":270},[33,6447,6448],{"class":39},"  \u002F\u002F The ErrorBoundary wraps the Suspense boundary — if the suspended fetch\n",[33,6450,6451],{"class":35,"line":276},[33,6452,6453],{"class":39},"  \u002F\u002F rejects, the error propagates to the ErrorBoundary, which shows its fallback.\n",[33,6455,6456],{"class":35,"line":282},[33,6457,482],{"class":73},[14,6459,6461],{"id":6460},"why-hand-rolled-fetching-hits-a-ceiling","Why Hand-Rolled Fetching Hits a Ceiling",[19,6463,6465],{"filename":6464,"language":22},"ceiling.js",[24,6466,6468],{"className":26,"code":6467,"language":22,"meta":28,"style":28},"\u002F\u002F A complete hand-rolled data-fetching solution would need to implement:\n\u002F\u002F\n\u002F\u002F Status tracking (loading\u002Ferror\u002Fsuccess)          — covered above\n\u002F\u002F Race condition protection (cancelled\u002FAbort)      — covered above\n\u002F\u002F Loading state debounce                           — covered above\n\u002F\u002F Retry with backoff                               — covered above\n\u002F\u002F Parallel fetching                                — covered above\n\u002F\u002F Caching across components                        — NOT covered (complex)\n\u002F\u002F Request deduplication                            — NOT covered (complex)\n\u002F\u002F Refetch on window focus                          — NOT covered (complex)\n\u002F\u002F Refetch on network reconnect                     — NOT covered (complex)\n\u002F\u002F Pagination\u002Finfinite scroll with data merging     — NOT covered (very complex)\n\u002F\u002F Optimistic updates with rollback                 — NOT covered (very complex)\n\u002F\u002F Prefetching for navigation                       — NOT covered (complex)\n\u002F\u002F Garbage collection of stale cache entries        — NOT covered (complex)\n\u002F\u002F\n\u002F\u002F Reimplementing ALL of this per-project is a multi-week investment that\n\u002F\u002F React Query and SWR already solved and battle-tested across thousands of\n\u002F\u002F production codebases. Reach for one the moment a project needs more than\n\u002F\u002F one of: caching, refetch-on-focus, deduplication, or pagination.\n\n\u002F\u002F WHEN hand-rolled fetching is FINE:\n\u002F\u002F - A single one-off fetch in a small component (use the cancelled-flag pattern)\n\u002F\u002F - A prototype or throwaway app\n\u002F\u002F - A learning exercise (understanding the fundamentals)\n\u002F\u002F - An app where the data layer is handled elsewhere (e.g. Next.js Server\n\u002F\u002F   Components, Remix loaders, tRPC)\n\n\u002F\u002F WHEN to reach for React Query \u002F SWR:\n\u002F\u002F - More than one component needs the same data (caching + dedup matter)\n\u002F\u002F - You need refetch-on-focus or reconnect\n\u002F\u002F - You need pagination, infinite scroll, or optimistic updates\n\u002F\u002F - You need to coordinate mutations with cache invalidation\n\u002F\u002F - You want DevTools to inspect query state\n",[30,6469,6470,6475,6480,6485,6490,6495,6500,6505,6510,6515,6520,6525,6530,6535,6540,6545,6549,6554,6559,6564,6569,6573,6578,6583,6588,6593,6598,6603,6607,6612,6617,6622,6627,6632],{"__ignoreMap":28},[33,6471,6472],{"class":35,"line":36},[33,6473,6474],{"class":39},"\u002F\u002F A complete hand-rolled data-fetching solution would need to implement:\n",[33,6476,6477],{"class":35,"line":43},[33,6478,6479],{"class":39},"\u002F\u002F\n",[33,6481,6482],{"class":35,"line":49},[33,6483,6484],{"class":39},"\u002F\u002F Status tracking (loading\u002Ferror\u002Fsuccess)          — covered above\n",[33,6486,6487],{"class":35,"line":55},[33,6488,6489],{"class":39},"\u002F\u002F Race condition protection (cancelled\u002FAbort)      — covered above\n",[33,6491,6492],{"class":35,"line":62},[33,6493,6494],{"class":39},"\u002F\u002F Loading state debounce                           — covered above\n",[33,6496,6497],{"class":35,"line":84},[33,6498,6499],{"class":39},"\u002F\u002F Retry with backoff                               — covered above\n",[33,6501,6502],{"class":35,"line":133},[33,6503,6504],{"class":39},"\u002F\u002F Parallel fetching                                — covered above\n",[33,6506,6507],{"class":35,"line":138},[33,6508,6509],{"class":39},"\u002F\u002F Caching across components                        — NOT covered (complex)\n",[33,6511,6512],{"class":35,"line":153},[33,6513,6514],{"class":39},"\u002F\u002F Request deduplication                            — NOT covered (complex)\n",[33,6516,6517],{"class":35,"line":173},[33,6518,6519],{"class":39},"\u002F\u002F Refetch on window focus                          — NOT covered (complex)\n",[33,6521,6522],{"class":35,"line":178},[33,6523,6524],{"class":39},"\u002F\u002F Refetch on network reconnect                     — NOT covered (complex)\n",[33,6526,6527],{"class":35,"line":198},[33,6528,6529],{"class":39},"\u002F\u002F Pagination\u002Finfinite scroll with data merging     — NOT covered (very complex)\n",[33,6531,6532],{"class":35,"line":217},[33,6533,6534],{"class":39},"\u002F\u002F Optimistic updates with rollback                 — NOT covered (very complex)\n",[33,6536,6537],{"class":35,"line":258},[33,6538,6539],{"class":39},"\u002F\u002F Prefetching for navigation                       — NOT covered (complex)\n",[33,6541,6542],{"class":35,"line":264},[33,6543,6544],{"class":39},"\u002F\u002F Garbage collection of stale cache entries        — NOT covered (complex)\n",[33,6546,6547],{"class":35,"line":270},[33,6548,6479],{"class":39},[33,6550,6551],{"class":35,"line":276},[33,6552,6553],{"class":39},"\u002F\u002F Reimplementing ALL of this per-project is a multi-week investment that\n",[33,6555,6556],{"class":35,"line":282},[33,6557,6558],{"class":39},"\u002F\u002F React Query and SWR already solved and battle-tested across thousands of\n",[33,6560,6561],{"class":35,"line":297},[33,6562,6563],{"class":39},"\u002F\u002F production codebases. Reach for one the moment a project needs more than\n",[33,6565,6566],{"class":35,"line":303},[33,6567,6568],{"class":39},"\u002F\u002F one of: caching, refetch-on-focus, deduplication, or pagination.\n",[33,6570,6571],{"class":35,"line":333},[33,6572,59],{"emptyLinePlaceholder":58},[33,6574,6575],{"class":35,"line":362},[33,6576,6577],{"class":39},"\u002F\u002F WHEN hand-rolled fetching is FINE:\n",[33,6579,6580],{"class":35,"line":368},[33,6581,6582],{"class":39},"\u002F\u002F - A single one-off fetch in a small component (use the cancelled-flag pattern)\n",[33,6584,6585],{"class":35,"line":373},[33,6586,6587],{"class":39},"\u002F\u002F - A prototype or throwaway app\n",[33,6589,6590],{"class":35,"line":403},[33,6591,6592],{"class":39},"\u002F\u002F - A learning exercise (understanding the fundamentals)\n",[33,6594,6595],{"class":35,"line":432},[33,6596,6597],{"class":39},"\u002F\u002F - An app where the data layer is handled elsewhere (e.g. Next.js Server\n",[33,6599,6600],{"class":35,"line":460},[33,6601,6602],{"class":39},"\u002F\u002F   Components, Remix loaders, tRPC)\n",[33,6604,6605],{"class":35,"line":479},[33,6606,59],{"emptyLinePlaceholder":58},[33,6608,6609],{"class":35,"line":1490},[33,6610,6611],{"class":39},"\u002F\u002F WHEN to reach for React Query \u002F SWR:\n",[33,6613,6614],{"class":35,"line":1496},[33,6615,6616],{"class":39},"\u002F\u002F - More than one component needs the same data (caching + dedup matter)\n",[33,6618,6619],{"class":35,"line":1502},[33,6620,6621],{"class":39},"\u002F\u002F - You need refetch-on-focus or reconnect\n",[33,6623,6624],{"class":35,"line":2301},[33,6625,6626],{"class":39},"\u002F\u002F - You need pagination, infinite scroll, or optimistic updates\n",[33,6628,6629],{"class":35,"line":2328},[33,6630,6631],{"class":39},"\u002F\u002F - You need to coordinate mutations with cache invalidation\n",[33,6633,6634],{"class":35,"line":2333},[33,6635,6636],{"class":39},"\u002F\u002F - You want DevTools to inspect query state\n",[14,6638,6640],{"id":6639},"tips-tricks","💡 Tips & Tricks",[19,6642,6644],{"filename":6643,"language":22},"tips.js",[24,6645,6647],{"className":26,"code":6646,"language":22,"meta":28,"style":28},"\u002F\u002F [Debug] Always check res.ok (or res.status) explicitly after fetch() — a 404\n\u002F\u002F or 500 is NOT a rejected promise. Skipping this check is the #1 cause of \"the\n\u002F\u002F error state never shows\" bugs in hand-rolled fetch code.\n\n\u002F\u002F [Idiom] Use the cancelled-flag as the default race guard for simple fetches;\n\u002F\u002F upgrade to AbortController when a request is expensive enough that actually\n\u002F\u002F cancelling it (not just ignoring its result) matters.\n\n\u002F\u002F [Idiom] Delay showing a loading spinner by ~200-300ms — most real requests\n\u002F\u002F resolve faster than a user can register a flash, and an instant-appearing,\n\u002F\u002F instant-disappearing spinner reads as visual noise.\n\n\u002F\u002F [Performance] Use Promise.all for independent requests, Promise.allSettled\n\u002F\u002F when partial success is acceptable (show what loaded, error indicator for what\n\u002F\u002F didn't). Waterfall ONLY when a request depends on data from a previous one.\n\n\u002F\u002F [Performance] Reach for React Query or SWR the moment a project needs more\n\u002F\u002F than one of: caching across components, refetch-on-focus, deduplication, or\n\u002F\u002F pagination — re-deriving all of these by hand is a multi-day investment\n\u002F\u002F libraries already solved and battle-tested.\n\n\u002F\u002F [Debug] When testing retry\u002Fbackoff logic, point at an endpoint that always\n\u002F\u002F fails (or throttle the network in DevTools) rather than relying on a real\n\u002F\u002F flaky service to happen to fail during testing — deterministic failure is\n\u002F\u002F faster to iterate against.\n\n\u002F\u002F [Idiom] Distinguish isLoading (no data yet) from isFetching (background\n\u002F\u002F refetch with cached data present) in your UI — use isLoading for full-screen\n\u002F\u002F spinners, isFetching for subtle indicators like a top-bar shimmer.\n\n\u002F\u002F [Idiom] In React Query, set staleTime > 0 for data that changes rarely\n\u002F\u002F (products, config) to avoid unnecessary refetches on every focus. Use\n\u002F\u002F refetchInterval for live data (scores, dashboards).\n",[30,6648,6649,6654,6659,6664,6668,6673,6678,6683,6687,6692,6697,6702,6706,6711,6716,6721,6725,6730,6735,6740,6745,6749,6754,6759,6764,6769,6773,6778,6783,6788,6792,6797,6802],{"__ignoreMap":28},[33,6650,6651],{"class":35,"line":36},[33,6652,6653],{"class":39},"\u002F\u002F [Debug] Always check res.ok (or res.status) explicitly after fetch() — a 404\n",[33,6655,6656],{"class":35,"line":43},[33,6657,6658],{"class":39},"\u002F\u002F or 500 is NOT a rejected promise. Skipping this check is the #1 cause of \"the\n",[33,6660,6661],{"class":35,"line":49},[33,6662,6663],{"class":39},"\u002F\u002F error state never shows\" bugs in hand-rolled fetch code.\n",[33,6665,6666],{"class":35,"line":55},[33,6667,59],{"emptyLinePlaceholder":58},[33,6669,6670],{"class":35,"line":62},[33,6671,6672],{"class":39},"\u002F\u002F [Idiom] Use the cancelled-flag as the default race guard for simple fetches;\n",[33,6674,6675],{"class":35,"line":84},[33,6676,6677],{"class":39},"\u002F\u002F upgrade to AbortController when a request is expensive enough that actually\n",[33,6679,6680],{"class":35,"line":133},[33,6681,6682],{"class":39},"\u002F\u002F cancelling it (not just ignoring its result) matters.\n",[33,6684,6685],{"class":35,"line":138},[33,6686,59],{"emptyLinePlaceholder":58},[33,6688,6689],{"class":35,"line":153},[33,6690,6691],{"class":39},"\u002F\u002F [Idiom] Delay showing a loading spinner by ~200-300ms — most real requests\n",[33,6693,6694],{"class":35,"line":173},[33,6695,6696],{"class":39},"\u002F\u002F resolve faster than a user can register a flash, and an instant-appearing,\n",[33,6698,6699],{"class":35,"line":178},[33,6700,6701],{"class":39},"\u002F\u002F instant-disappearing spinner reads as visual noise.\n",[33,6703,6704],{"class":35,"line":198},[33,6705,59],{"emptyLinePlaceholder":58},[33,6707,6708],{"class":35,"line":217},[33,6709,6710],{"class":39},"\u002F\u002F [Performance] Use Promise.all for independent requests, Promise.allSettled\n",[33,6712,6713],{"class":35,"line":258},[33,6714,6715],{"class":39},"\u002F\u002F when partial success is acceptable (show what loaded, error indicator for what\n",[33,6717,6718],{"class":35,"line":264},[33,6719,6720],{"class":39},"\u002F\u002F didn't). Waterfall ONLY when a request depends on data from a previous one.\n",[33,6722,6723],{"class":35,"line":270},[33,6724,59],{"emptyLinePlaceholder":58},[33,6726,6727],{"class":35,"line":276},[33,6728,6729],{"class":39},"\u002F\u002F [Performance] Reach for React Query or SWR the moment a project needs more\n",[33,6731,6732],{"class":35,"line":282},[33,6733,6734],{"class":39},"\u002F\u002F than one of: caching across components, refetch-on-focus, deduplication, or\n",[33,6736,6737],{"class":35,"line":297},[33,6738,6739],{"class":39},"\u002F\u002F pagination — re-deriving all of these by hand is a multi-day investment\n",[33,6741,6742],{"class":35,"line":303},[33,6743,6744],{"class":39},"\u002F\u002F libraries already solved and battle-tested.\n",[33,6746,6747],{"class":35,"line":333},[33,6748,59],{"emptyLinePlaceholder":58},[33,6750,6751],{"class":35,"line":362},[33,6752,6753],{"class":39},"\u002F\u002F [Debug] When testing retry\u002Fbackoff logic, point at an endpoint that always\n",[33,6755,6756],{"class":35,"line":368},[33,6757,6758],{"class":39},"\u002F\u002F fails (or throttle the network in DevTools) rather than relying on a real\n",[33,6760,6761],{"class":35,"line":373},[33,6762,6763],{"class":39},"\u002F\u002F flaky service to happen to fail during testing — deterministic failure is\n",[33,6765,6766],{"class":35,"line":403},[33,6767,6768],{"class":39},"\u002F\u002F faster to iterate against.\n",[33,6770,6771],{"class":35,"line":432},[33,6772,59],{"emptyLinePlaceholder":58},[33,6774,6775],{"class":35,"line":460},[33,6776,6777],{"class":39},"\u002F\u002F [Idiom] Distinguish isLoading (no data yet) from isFetching (background\n",[33,6779,6780],{"class":35,"line":479},[33,6781,6782],{"class":39},"\u002F\u002F refetch with cached data present) in your UI — use isLoading for full-screen\n",[33,6784,6785],{"class":35,"line":1490},[33,6786,6787],{"class":39},"\u002F\u002F spinners, isFetching for subtle indicators like a top-bar shimmer.\n",[33,6789,6790],{"class":35,"line":1496},[33,6791,59],{"emptyLinePlaceholder":58},[33,6793,6794],{"class":35,"line":1502},[33,6795,6796],{"class":39},"\u002F\u002F [Idiom] In React Query, set staleTime > 0 for data that changes rarely\n",[33,6798,6799],{"class":35,"line":2301},[33,6800,6801],{"class":39},"\u002F\u002F (products, config) to avoid unnecessary refetches on every focus. Use\n",[33,6803,6804],{"class":35,"line":2328},[33,6805,6806],{"class":39},"\u002F\u002F refetchInterval for live data (scores, dashboards).\n",[14,6808,6810],{"id":6809},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,6812,6814],{"filename":6813,"language":22},"edge_cases.js",[24,6815,6817],{"className":26,"code":6816,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] fetch() only rejects for network-level failures, NEVER for HTTP error\n\u002F\u002F status codes — a 404, 401, or 500 resolves the promise successfully. Always\n\u002F\u002F check response.ok or response.status before treating a response as valid data.\n\n\u002F\u002F [Gotcha] Fast successive requests can resolve out of order, letting a stale\n\u002F\u002F response overwrite fresher data with no error or warning — a genuine race\n\u002F\u002F condition, not a hypothetical one. Shows up constantly in search-as-you-type\n\u002F\u002F and rapidly-changing-filter UIs. Guard with cancelled flag or AbortController.\n\n\u002F\u002F [Gotcha] AbortController's abort produces a rejected promise with\n\u002F\u002F err.name === 'AbortError', which must be distinguished from genuine failures\n\u002F\u002F in .catch() — treating every abort as a real error surfaces a misleading\n\u002F\u002F error message every time a newer request supersedes an older one.\n\n\u002F\u002F [Gotcha] Retrying with no backoff (or worse, retrying on every render due to\n\u002F\u002F a missing\u002Fincorrect dependency array) can amplify load on a struggling server\n\u002F\u002F precisely when it's least able to handle it. Always space retries with\n\u002F\u002F exponential backoff, always scope retry logic inside useEffect with a correct\n\u002F\u002F dependency array, NEVER inline in the render body.\n\n\u002F\u002F [Gotcha] Data-fetching libraries' caching means a component can render\n\u002F\u002F \"instantly\" with stale cached data before a background refetch completes —\n\u002F\u002F this is the intended stale-while-revalidate behavior, not a bug. isLoading\n\u002F\u002F (no data yet) and isFetching (background refetch, data present) are\n\u002F\u002F meaningfully different states that a naive migration often conflates.\n\n\u002F\u002F [Gotcha] Promise.all rejects entirely if ANY single promise rejects — if you\n\u002F\u002F need partial success (some data even if one request fails), use\n\u002F\u002F Promise.allSettled and check each result's status individually.\n\n\u002F\u002F [Gotcha] In React 18 Strict Mode (dev), effects run twice (mount → unmount →\n\u002F\u002F mount). This means useEffect-based fetches fire twice in development — your\n\u002F\u002F AbortController cleanup MUST work correctly or you'll see double requests and\n\u002F\u002F potential state-update-after-unmount warnings. React Query\u002FSWR handle this\n\u002F\u002F automatically via their deduplication.\n\n\u002F\u002F [Gotcha] JSON.parse can throw on malformed responses (e.g. a 200 OK with HTML\n\u002F\u002F body from a misconfigured proxy) — wrap res.json() in try\u002Fcatch or handle the\n\u002F\u002F rejection, or you'll get an unhandled rejection with no useful error message.\n",[30,6818,6819,6824,6829,6834,6838,6843,6848,6853,6858,6862,6867,6872,6877,6882,6886,6891,6896,6901,6906,6911,6915,6920,6925,6930,6935,6940,6944,6949,6954,6959,6963,6968,6973,6978,6983,6988,6992,6997,7002],{"__ignoreMap":28},[33,6820,6821],{"class":35,"line":36},[33,6822,6823],{"class":39},"\u002F\u002F [Gotcha] fetch() only rejects for network-level failures, NEVER for HTTP error\n",[33,6825,6826],{"class":35,"line":43},[33,6827,6828],{"class":39},"\u002F\u002F status codes — a 404, 401, or 500 resolves the promise successfully. Always\n",[33,6830,6831],{"class":35,"line":49},[33,6832,6833],{"class":39},"\u002F\u002F check response.ok or response.status before treating a response as valid data.\n",[33,6835,6836],{"class":35,"line":55},[33,6837,59],{"emptyLinePlaceholder":58},[33,6839,6840],{"class":35,"line":62},[33,6841,6842],{"class":39},"\u002F\u002F [Gotcha] Fast successive requests can resolve out of order, letting a stale\n",[33,6844,6845],{"class":35,"line":84},[33,6846,6847],{"class":39},"\u002F\u002F response overwrite fresher data with no error or warning — a genuine race\n",[33,6849,6850],{"class":35,"line":133},[33,6851,6852],{"class":39},"\u002F\u002F condition, not a hypothetical one. Shows up constantly in search-as-you-type\n",[33,6854,6855],{"class":35,"line":138},[33,6856,6857],{"class":39},"\u002F\u002F and rapidly-changing-filter UIs. Guard with cancelled flag or AbortController.\n",[33,6859,6860],{"class":35,"line":153},[33,6861,59],{"emptyLinePlaceholder":58},[33,6863,6864],{"class":35,"line":173},[33,6865,6866],{"class":39},"\u002F\u002F [Gotcha] AbortController's abort produces a rejected promise with\n",[33,6868,6869],{"class":35,"line":178},[33,6870,6871],{"class":39},"\u002F\u002F err.name === 'AbortError', which must be distinguished from genuine failures\n",[33,6873,6874],{"class":35,"line":198},[33,6875,6876],{"class":39},"\u002F\u002F in .catch() — treating every abort as a real error surfaces a misleading\n",[33,6878,6879],{"class":35,"line":217},[33,6880,6881],{"class":39},"\u002F\u002F error message every time a newer request supersedes an older one.\n",[33,6883,6884],{"class":35,"line":258},[33,6885,59],{"emptyLinePlaceholder":58},[33,6887,6888],{"class":35,"line":264},[33,6889,6890],{"class":39},"\u002F\u002F [Gotcha] Retrying with no backoff (or worse, retrying on every render due to\n",[33,6892,6893],{"class":35,"line":270},[33,6894,6895],{"class":39},"\u002F\u002F a missing\u002Fincorrect dependency array) can amplify load on a struggling server\n",[33,6897,6898],{"class":35,"line":276},[33,6899,6900],{"class":39},"\u002F\u002F precisely when it's least able to handle it. Always space retries with\n",[33,6902,6903],{"class":35,"line":282},[33,6904,6905],{"class":39},"\u002F\u002F exponential backoff, always scope retry logic inside useEffect with a correct\n",[33,6907,6908],{"class":35,"line":297},[33,6909,6910],{"class":39},"\u002F\u002F dependency array, NEVER inline in the render body.\n",[33,6912,6913],{"class":35,"line":303},[33,6914,59],{"emptyLinePlaceholder":58},[33,6916,6917],{"class":35,"line":333},[33,6918,6919],{"class":39},"\u002F\u002F [Gotcha] Data-fetching libraries' caching means a component can render\n",[33,6921,6922],{"class":35,"line":362},[33,6923,6924],{"class":39},"\u002F\u002F \"instantly\" with stale cached data before a background refetch completes —\n",[33,6926,6927],{"class":35,"line":368},[33,6928,6929],{"class":39},"\u002F\u002F this is the intended stale-while-revalidate behavior, not a bug. isLoading\n",[33,6931,6932],{"class":35,"line":373},[33,6933,6934],{"class":39},"\u002F\u002F (no data yet) and isFetching (background refetch, data present) are\n",[33,6936,6937],{"class":35,"line":403},[33,6938,6939],{"class":39},"\u002F\u002F meaningfully different states that a naive migration often conflates.\n",[33,6941,6942],{"class":35,"line":432},[33,6943,59],{"emptyLinePlaceholder":58},[33,6945,6946],{"class":35,"line":460},[33,6947,6948],{"class":39},"\u002F\u002F [Gotcha] Promise.all rejects entirely if ANY single promise rejects — if you\n",[33,6950,6951],{"class":35,"line":479},[33,6952,6953],{"class":39},"\u002F\u002F need partial success (some data even if one request fails), use\n",[33,6955,6956],{"class":35,"line":1490},[33,6957,6958],{"class":39},"\u002F\u002F Promise.allSettled and check each result's status individually.\n",[33,6960,6961],{"class":35,"line":1496},[33,6962,59],{"emptyLinePlaceholder":58},[33,6964,6965],{"class":35,"line":1502},[33,6966,6967],{"class":39},"\u002F\u002F [Gotcha] In React 18 Strict Mode (dev), effects run twice (mount → unmount →\n",[33,6969,6970],{"class":35,"line":2301},[33,6971,6972],{"class":39},"\u002F\u002F mount). This means useEffect-based fetches fire twice in development — your\n",[33,6974,6975],{"class":35,"line":2328},[33,6976,6977],{"class":39},"\u002F\u002F AbortController cleanup MUST work correctly or you'll see double requests and\n",[33,6979,6980],{"class":35,"line":2333},[33,6981,6982],{"class":39},"\u002F\u002F potential state-update-after-unmount warnings. React Query\u002FSWR handle this\n",[33,6984,6985],{"class":35,"line":2338},[33,6986,6987],{"class":39},"\u002F\u002F automatically via their deduplication.\n",[33,6989,6990],{"class":35,"line":2847},[33,6991,59],{"emptyLinePlaceholder":58},[33,6993,6994],{"class":35,"line":2874},[33,6995,6996],{"class":39},"\u002F\u002F [Gotcha] JSON.parse can throw on malformed responses (e.g. a 200 OK with HTML\n",[33,6998,6999],{"class":35,"line":2891},[33,7000,7001],{"class":39},"\u002F\u002F body from a misconfigured proxy) — wrap res.json() in try\u002Fcatch or handle the\n",[33,7003,7004],{"class":35,"line":2896},[33,7005,7006],{"class":39},"\u002F\u002F rejection, or you'll get an unhandled rejection with no useful error message.\n",[14,7008,7010],{"id":7009},"spot-the-bug","🧠 Spot the Bug",[2562,7012,7013],{},"An autocomplete search box occasionally flashes results for an old, already-abandoned query text before settling on the correct results:",[19,7015,7017],{"filename":7016,"language":22},"spot_the_bug.js",[24,7018,7020],{"className":26,"code":7019,"language":22,"meta":28,"style":28},"function Autocomplete({ query }) {\n  const [suggestions, setSuggestions] = useState([])\n\n  useEffect(() => {\n    fetch(`\u002Fapi\u002Fsuggest?q=${query}`)\n      .then(res => res.json())\n      .then(data => setSuggestions(data.suggestions))\n  }, [query])\n\n  return (\n    \u003Cul>\n      {suggestions.map(s => \u003Cli key={s}>{s}\u003C\u002Fli>)}\n    \u003C\u002Ful>\n  )\n}\n",[30,7021,7022,7035,7057,7061,7071,7086,7104,7122,7126,7130,7136,7144,7174,7182,7186],{"__ignoreMap":28},[33,7023,7024,7026,7029,7031,7033],{"class":35,"line":36},[33,7025,66],{"class":65},[33,7027,7028],{"class":69}," Autocomplete",[33,7030,74],{"class":73},[33,7032,767],{"class":77},[33,7034,81],{"class":73},[33,7036,7037,7039,7041,7044,7046,7049,7051,7053,7055],{"class":35,"line":43},[33,7038,87],{"class":65},[33,7040,90],{"class":73},[33,7042,7043],{"class":93},"suggestions",[33,7045,97],{"class":73},[33,7047,7048],{"class":93},"setSuggestions",[33,7050,103],{"class":73},[33,7052,106],{"class":65},[33,7054,109],{"class":69},[33,7056,792],{"class":73},[33,7058,7059],{"class":35,"line":49},[33,7060,59],{"emptyLinePlaceholder":58},[33,7062,7063,7065,7067,7069],{"class":35,"line":55},[33,7064,141],{"class":69},[33,7066,144],{"class":73},[33,7068,147],{"class":65},[33,7070,150],{"class":73},[33,7072,7073,7075,7077,7080,7082,7084],{"class":35,"line":62},[33,7074,181],{"class":69},[33,7076,184],{"class":73},[33,7078,7079],{"class":115},"`\u002Fapi\u002Fsuggest?q=${",[33,7081,767],{"class":73},[33,7083,192],{"class":115},[33,7085,195],{"class":73},[33,7087,7088,7090,7092,7094,7096,7098,7100,7102],{"class":35,"line":84},[33,7089,201],{"class":73},[33,7091,204],{"class":69},[33,7093,184],{"class":73},[33,7095,209],{"class":77},[33,7097,212],{"class":65},[33,7099,288],{"class":73},[33,7101,291],{"class":69},[33,7103,626],{"class":73},[33,7105,7106,7108,7110,7112,7114,7116,7119],{"class":35,"line":133},[33,7107,201],{"class":73},[33,7109,204],{"class":69},[33,7111,184],{"class":73},[33,7113,312],{"class":77},[33,7115,212],{"class":65},[33,7117,7118],{"class":69}," setSuggestions",[33,7120,7121],{"class":73},"(data.suggestions))\n",[33,7123,7124],{"class":35,"line":138},[33,7125,887],{"class":73},[33,7127,7128],{"class":35,"line":153},[33,7129,59],{"emptyLinePlaceholder":58},[33,7131,7132,7134],{"class":35,"line":173},[33,7133,463],{"class":65},[33,7135,3845],{"class":73},[33,7137,7138,7140,7142],{"class":35,"line":178},[33,7139,3850],{"class":73},[33,7141,901],{"class":900},[33,7143,937],{"class":73},[33,7145,7146,7149,7151,7153,7156,7158,7160,7162,7164,7166,7169,7171],{"class":35,"line":198},[33,7147,7148],{"class":73},"      {suggestions.",[33,7150,907],{"class":69},[33,7152,184],{"class":73},[33,7154,7155],{"class":77},"s",[33,7157,212],{"class":65},[33,7159,394],{"class":73},[33,7161,919],{"class":900},[33,7163,922],{"class":69},[33,7165,106],{"class":65},[33,7167,7168],{"class":73},"{s}>{s}\u003C\u002F",[33,7170,919],{"class":900},[33,7172,7173],{"class":73},">)}\n",[33,7175,7176,7178,7180],{"class":35,"line":217},[33,7177,3889],{"class":73},[33,7179,901],{"class":900},[33,7181,937],{"class":73},[33,7183,7184],{"class":35,"line":258},[33,7185,3898],{"class":73},[33,7187,7188],{"class":35,"line":264},[33,7189,482],{"class":73},[7191,7192,7193,7197,7216,7228,7448],"details",{},[7194,7195,7196],"summary",{},"Answer",[2562,7198,7199,7200,7203,7204,7207,7208,7210,7211,7215],{},"There is no guard against out-of-order resolution — every keystroke fires a new ",[30,7201,7202],{},"fetch",", and nothing prevents an older, slower-resolving request's ",[30,7205,7206],{},".then()"," from calling ",[30,7209,7048],{}," ",[7212,7213,7214],"strong",{},"after"," a newer, faster-resolving request has already set the correct results. Whichever request resolves last wins, regardless of which query text it was actually for.",[2562,7217,7218,7221,7222,7224,7225,7227],{},[7212,7219,7220],{},"Fix",": add a ",[30,7223,948],{}," flag or ",[30,7226,1206],{}," tied to the effect's cleanup:",[24,7229,7231],{"className":26,"code":7230,"language":22,"meta":28,"style":28},"function Autocomplete({ query }) {\n  const [suggestions, setSuggestions] = useState([])\n\n  useEffect(() => {\n    const controller = new AbortController()\n\n    fetch(`\u002Fapi\u002Fsuggest?q=${query}`, { signal: controller.signal })\n      .then(res => res.json())\n      .then(data => setSuggestions(data.suggestions))\n      .catch(err => {\n        if (err.name !== 'AbortError') console.error(err)\n      })\n\n    return () => controller.abort()\n  }, [query])\n\n  return \u003Cul>{suggestions.map(s => \u003Cli key={s}>{s}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n",[30,7232,7233,7245,7265,7269,7279,7293,7297,7311,7329,7345,7359,7377,7381,7385,7399,7403,7407,7444],{"__ignoreMap":28},[33,7234,7235,7237,7239,7241,7243],{"class":35,"line":36},[33,7236,66],{"class":65},[33,7238,7028],{"class":69},[33,7240,74],{"class":73},[33,7242,767],{"class":77},[33,7244,81],{"class":73},[33,7246,7247,7249,7251,7253,7255,7257,7259,7261,7263],{"class":35,"line":43},[33,7248,87],{"class":65},[33,7250,90],{"class":73},[33,7252,7043],{"class":93},[33,7254,97],{"class":73},[33,7256,7048],{"class":93},[33,7258,103],{"class":73},[33,7260,106],{"class":65},[33,7262,109],{"class":69},[33,7264,792],{"class":73},[33,7266,7267],{"class":35,"line":49},[33,7268,59],{"emptyLinePlaceholder":58},[33,7270,7271,7273,7275,7277],{"class":35,"line":55},[33,7272,141],{"class":69},[33,7274,144],{"class":73},[33,7276,147],{"class":65},[33,7278,150],{"class":73},[33,7280,7281,7283,7285,7287,7289,7291],{"class":35,"line":62},[33,7282,1285],{"class":65},[33,7284,1288],{"class":93},[33,7286,1291],{"class":65},[33,7288,235],{"class":65},[33,7290,1296],{"class":69},[33,7292,294],{"class":73},[33,7294,7295],{"class":35,"line":84},[33,7296,59],{"emptyLinePlaceholder":58},[33,7298,7299,7301,7303,7305,7307,7309],{"class":35,"line":133},[33,7300,181],{"class":69},[33,7302,184],{"class":73},[33,7304,7079],{"class":115},[33,7306,767],{"class":73},[33,7308,192],{"class":115},[33,7310,1317],{"class":73},[33,7312,7313,7315,7317,7319,7321,7323,7325,7327],{"class":35,"line":138},[33,7314,201],{"class":73},[33,7316,204],{"class":69},[33,7318,184],{"class":73},[33,7320,209],{"class":77},[33,7322,212],{"class":65},[33,7324,288],{"class":73},[33,7326,291],{"class":69},[33,7328,626],{"class":73},[33,7330,7331,7333,7335,7337,7339,7341,7343],{"class":35,"line":153},[33,7332,201],{"class":73},[33,7334,204],{"class":69},[33,7336,184],{"class":73},[33,7338,312],{"class":77},[33,7340,212],{"class":65},[33,7342,7118],{"class":69},[33,7344,7121],{"class":73},[33,7346,7347,7349,7351,7353,7355,7357],{"class":35,"line":173},[33,7348,201],{"class":73},[33,7350,338],{"class":69},[33,7352,184],{"class":73},[33,7354,1362],{"class":77},[33,7356,212],{"class":65},[33,7358,150],{"class":73},[33,7360,7361,7363,7365,7367,7369,7372,7374],{"class":35,"line":178},[33,7362,220],{"class":65},[33,7364,1373],{"class":73},[33,7366,1376],{"class":65},[33,7368,1379],{"class":115},[33,7370,7371],{"class":73},") console.",[33,7373,343],{"class":69},[33,7375,7376],{"class":73},"(err)\n",[33,7378,7379],{"class":35,"line":198},[33,7380,300],{"class":73},[33,7382,7383],{"class":35,"line":217},[33,7384,59],{"emptyLinePlaceholder":58},[33,7386,7387,7389,7391,7393,7395,7397],{"class":35,"line":258},[33,7388,1134],{"class":65},[33,7390,1137],{"class":73},[33,7392,147],{"class":65},[33,7394,1425],{"class":73},[33,7396,1428],{"class":69},[33,7398,294],{"class":73},[33,7400,7401],{"class":35,"line":264},[33,7402,887],{"class":73},[33,7404,7405],{"class":35,"line":270},[33,7406,59],{"emptyLinePlaceholder":58},[33,7408,7409,7411,7413,7415,7418,7420,7422,7424,7426,7428,7430,7432,7434,7436,7438,7440,7442],{"class":35,"line":276},[33,7410,463],{"class":65},[33,7412,394],{"class":73},[33,7414,901],{"class":900},[33,7416,7417],{"class":73},">{suggestions.",[33,7419,907],{"class":69},[33,7421,184],{"class":73},[33,7423,7155],{"class":77},[33,7425,212],{"class":65},[33,7427,394],{"class":73},[33,7429,919],{"class":900},[33,7431,922],{"class":69},[33,7433,106],{"class":65},[33,7435,7168],{"class":73},[33,7437,919],{"class":900},[33,7439,932],{"class":73},[33,7441,901],{"class":900},[33,7443,937],{"class":73},[33,7445,7446],{"class":35,"line":282},[33,7447,482],{"class":73},[2562,7449,7450,7451,7454],{},"The lesson: any ",[30,7452,7453],{},"useEffect","-based fetch whose triggering value can change again before the current request resolves needs an explicit guard — without one, \"last requested\" and \"last resolved\" are two different things, and only one is safe to trust.",[14,7456,7458],{"id":7457},"key-takeaways","Key Takeaways",[19,7460,7462],{"filename":7461,"language":22},"key_takeaways.js",[24,7463,7465],{"className":26,"code":7464,"language":22,"meta":28,"style":28},"\u002F\u002F 1. A production fetch needs at least loading\u002Ferror\u002Fsuccess states, often an\n\u002F\u002F    explicit empty-success state. fetch() NEVER rejects for HTTP error statuses\n\u002F\u002F    — always check res.ok manually.\n\n\u002F\u002F 2. Fast successive requests resolve out of order — guard every useEffect-based\n\u002F\u002F    fetch with a cancelled flag or AbortController tied to the effect's cleanup.\n\u002F\u002F    AbortController additionally cancels the underlying network request, saving\n\u002F\u002F    bandwidth for expensive or high-frequency fetches.\n\n\u002F\u002F 3. Delay showing loading indicators by ~200-300ms to avoid flashing a spinner\n\u002F\u002F    for requests that resolve almost instantly.\n\n\u002F\u002F 4. Retry transient failures with exponential backoff — immediate, unbounded\n\u002F\u002F    retries worsen load on an already-struggling server.\n\n\u002F\u002F 5. Use Promise.all for parallel independent requests (total = slowest, not\n\u002F\u002F    sum). Use Promise.allSettled when partial success is acceptable. Waterfall\n\u002F\u002F    ONLY when a request depends on data from a previous one.\n\n\u002F\u002F 6. React Query and SWR solve caching, deduplication, refetch-on-focus, race\n\u002F\u002F    conditions, pagination, and optimistic updates that hand-rolled useEffect\n\u002F\u002F    fetching handles poorly or not at all. Reach for one once data needs exceed\n\u002F\u002F    simple one-off fetches.\n\n\u002F\u002F 7. Distinguish isLoading (no data yet, full spinner) from isFetching (background\n\u002F\u002F    refetch with cached data, subtle indicator) — conflating them is the most\n\u002F\u002F    common migration mistake from hand-rolled to library-based fetching.\n\n\u002F\u002F 8. Suspense for data fetching (React 18+) lets components declaratively suspend\n\u002F\u002F    while waiting for data — the parent \u003CSuspense> handles the fallback, and\n\u002F\u002F    an ErrorBoundary handles fetch errors. No null checks needed in the suspended\n\u002F\u002F    component (data is always defined when it renders).\n\n\u002F\u002F 9. React Query vs SWR: React Query for complex caching\u002Fmutation needs and rich\n\u002F\u002F    DevTools; SWR for smaller apps wanting the smallest bundle and implicit\n\u002F\u002F    stale-while-revalidate behavior.\n",[30,7466,7467,7472,7477,7482,7486,7491,7496,7501,7506,7510,7515,7520,7524,7529,7534,7538,7543,7548,7553,7557,7562,7567,7572,7577,7581,7586,7591,7596,7600,7605,7610,7615,7620,7624,7629,7634],{"__ignoreMap":28},[33,7468,7469],{"class":35,"line":36},[33,7470,7471],{"class":39},"\u002F\u002F 1. A production fetch needs at least loading\u002Ferror\u002Fsuccess states, often an\n",[33,7473,7474],{"class":35,"line":43},[33,7475,7476],{"class":39},"\u002F\u002F    explicit empty-success state. fetch() NEVER rejects for HTTP error statuses\n",[33,7478,7479],{"class":35,"line":49},[33,7480,7481],{"class":39},"\u002F\u002F    — always check res.ok manually.\n",[33,7483,7484],{"class":35,"line":55},[33,7485,59],{"emptyLinePlaceholder":58},[33,7487,7488],{"class":35,"line":62},[33,7489,7490],{"class":39},"\u002F\u002F 2. Fast successive requests resolve out of order — guard every useEffect-based\n",[33,7492,7493],{"class":35,"line":84},[33,7494,7495],{"class":39},"\u002F\u002F    fetch with a cancelled flag or AbortController tied to the effect's cleanup.\n",[33,7497,7498],{"class":35,"line":133},[33,7499,7500],{"class":39},"\u002F\u002F    AbortController additionally cancels the underlying network request, saving\n",[33,7502,7503],{"class":35,"line":138},[33,7504,7505],{"class":39},"\u002F\u002F    bandwidth for expensive or high-frequency fetches.\n",[33,7507,7508],{"class":35,"line":153},[33,7509,59],{"emptyLinePlaceholder":58},[33,7511,7512],{"class":35,"line":173},[33,7513,7514],{"class":39},"\u002F\u002F 3. Delay showing loading indicators by ~200-300ms to avoid flashing a spinner\n",[33,7516,7517],{"class":35,"line":178},[33,7518,7519],{"class":39},"\u002F\u002F    for requests that resolve almost instantly.\n",[33,7521,7522],{"class":35,"line":198},[33,7523,59],{"emptyLinePlaceholder":58},[33,7525,7526],{"class":35,"line":217},[33,7527,7528],{"class":39},"\u002F\u002F 4. Retry transient failures with exponential backoff — immediate, unbounded\n",[33,7530,7531],{"class":35,"line":258},[33,7532,7533],{"class":39},"\u002F\u002F    retries worsen load on an already-struggling server.\n",[33,7535,7536],{"class":35,"line":264},[33,7537,59],{"emptyLinePlaceholder":58},[33,7539,7540],{"class":35,"line":270},[33,7541,7542],{"class":39},"\u002F\u002F 5. Use Promise.all for parallel independent requests (total = slowest, not\n",[33,7544,7545],{"class":35,"line":276},[33,7546,7547],{"class":39},"\u002F\u002F    sum). Use Promise.allSettled when partial success is acceptable. Waterfall\n",[33,7549,7550],{"class":35,"line":282},[33,7551,7552],{"class":39},"\u002F\u002F    ONLY when a request depends on data from a previous one.\n",[33,7554,7555],{"class":35,"line":297},[33,7556,59],{"emptyLinePlaceholder":58},[33,7558,7559],{"class":35,"line":303},[33,7560,7561],{"class":39},"\u002F\u002F 6. React Query and SWR solve caching, deduplication, refetch-on-focus, race\n",[33,7563,7564],{"class":35,"line":333},[33,7565,7566],{"class":39},"\u002F\u002F    conditions, pagination, and optimistic updates that hand-rolled useEffect\n",[33,7568,7569],{"class":35,"line":362},[33,7570,7571],{"class":39},"\u002F\u002F    fetching handles poorly or not at all. Reach for one once data needs exceed\n",[33,7573,7574],{"class":35,"line":368},[33,7575,7576],{"class":39},"\u002F\u002F    simple one-off fetches.\n",[33,7578,7579],{"class":35,"line":373},[33,7580,59],{"emptyLinePlaceholder":58},[33,7582,7583],{"class":35,"line":403},[33,7584,7585],{"class":39},"\u002F\u002F 7. Distinguish isLoading (no data yet, full spinner) from isFetching (background\n",[33,7587,7588],{"class":35,"line":432},[33,7589,7590],{"class":39},"\u002F\u002F    refetch with cached data, subtle indicator) — conflating them is the most\n",[33,7592,7593],{"class":35,"line":460},[33,7594,7595],{"class":39},"\u002F\u002F    common migration mistake from hand-rolled to library-based fetching.\n",[33,7597,7598],{"class":35,"line":479},[33,7599,59],{"emptyLinePlaceholder":58},[33,7601,7602],{"class":35,"line":1490},[33,7603,7604],{"class":39},"\u002F\u002F 8. Suspense for data fetching (React 18+) lets components declaratively suspend\n",[33,7606,7607],{"class":35,"line":1496},[33,7608,7609],{"class":39},"\u002F\u002F    while waiting for data — the parent \u003CSuspense> handles the fallback, and\n",[33,7611,7612],{"class":35,"line":1502},[33,7613,7614],{"class":39},"\u002F\u002F    an ErrorBoundary handles fetch errors. No null checks needed in the suspended\n",[33,7616,7617],{"class":35,"line":2301},[33,7618,7619],{"class":39},"\u002F\u002F    component (data is always defined when it renders).\n",[33,7621,7622],{"class":35,"line":2328},[33,7623,59],{"emptyLinePlaceholder":58},[33,7625,7626],{"class":35,"line":2333},[33,7627,7628],{"class":39},"\u002F\u002F 9. React Query vs SWR: React Query for complex caching\u002Fmutation needs and rich\n",[33,7630,7631],{"class":35,"line":2338},[33,7632,7633],{"class":39},"\u002F\u002F    DevTools; SWR for smaller apps wanting the smallest bundle and implicit\n",[33,7635,7636],{"class":35,"line":2847},[33,7637,7638],{"class":39},"\u002F\u002F    stale-while-revalidate behavior.\n",[7640,7641,7642],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}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 pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":28,"searchDepth":43,"depth":43,"links":7644},[7645,7648,7654,7655,7656,7659,7663,7666,7668,7671,7672,7673,7674,7675],{"id":16,"depth":43,"text":17,"children":7646},[7647],{"id":486,"depth":49,"text":487},{"id":727,"depth":43,"text":728,"children":7649},[7650,7652],{"id":944,"depth":49,"text":7651},"Fix 1: The cancelled Flag",{"id":1202,"depth":49,"text":7653},"Fix 2: AbortController (Actually Cancels the Request)",{"id":1508,"depth":43,"text":1509},{"id":1846,"depth":43,"text":1847},{"id":2343,"depth":43,"text":2344,"children":7657},[7658],{"id":3048,"depth":49,"text":3049},{"id":3561,"depth":43,"text":3562,"children":7660},[7661,7662],{"id":3905,"depth":49,"text":3906},{"id":4310,"depth":49,"text":4311},{"id":4991,"depth":43,"text":4992,"children":7664},[7665],{"id":5492,"depth":49,"text":5493},{"id":5631,"depth":43,"text":7667},"isLoading vs isFetching: A Critical Distinction",{"id":5882,"depth":43,"text":5883,"children":7669},[7670],{"id":6308,"depth":49,"text":6309},{"id":6460,"depth":43,"text":6461},{"id":6639,"depth":43,"text":6640},{"id":6809,"depth":43,"text":6810},{"id":7009,"depth":43,"text":7010},{"id":7457,"depth":43,"text":7458},"useEffect + fetch and why it's not ideal, the four fetch states, race conditions and AbortController, loading state patterns, retry with backoff, waterfalls vs parallel fetching, React Query vs SWR comparison, caching strategies, and Suspense for data fetching (React 18+). Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F17-fetching-data",{"title":5,"description":7676},"react\u002F17-fetching-data","o85c3biqYZDTGa1MKp5RNj6jco9dhTiMDLrL3cEGRck",1789924652380]