[{"data":1,"prerenderedAt":5339},["ShallowReactive",2],{"page-\u002Freact\u002F21-concurrent-features":3},{"id":4,"title":5,"body":6,"description":5332,"extension":5333,"meta":5334,"navigation":57,"path":5335,"seo":5336,"stem":5337,"__hash__":5338},"content\u002Freact\u002F21-concurrent-features.md","21 — Concurrent Features",{"type":7,"value":8,"toc":5305},"minimark",[9,13,18,322,327,445,449,805,809,1043,1047,1391,1395,1779,1783,2132,2136,2486,2490,2752,2756,2958,2962,3101,3105,3310,3314,3521,3525,3655,3659,4456,4460,4593,4597,4779,4783,4789,5000,5160,5164,5301],[10,11,5],"h1",{"id":12},"_21-concurrent-features",[14,15,17],"h2",{"id":16},"automatic-batching-react-18s-default-behavior","Automatic Batching — React 18's Default Behavior",[19,20,23],"code-wrapper",{"filename":21,"language":22},"automatic_batching.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","import { useState } from 'react'\n\n\u002F\u002F Before React 18: only setState calls inside React event handlers were batched.\n\u002F\u002F setState inside setTimeout, promises, or native listeners each triggered\n\u002F\u002F a SEPARATE re-render. React 18 batches ALL setState calls regardless of\n\u002F\u002F where they occur — a genuine behavioral change, not just an optimization.\n\nfunction ProductForm() {\n  const [isSaving, setIsSaving] = useState(false)\n  const [error, setError] = useState(null)\n\n  async function handleSave() {\n    setIsSaving(true)\n    try {\n      await saveProduct()\n    } catch (err) {\n      \u002F\u002F React 18: both setState calls below are batched into ONE re-render.\n      \u002F\u002F React 17: each triggered its own separate re-render (two renders).\n      setError(err)\n      setIsSaving(false)\n    }\n  }\n\n  return \u003Cbutton onClick={handleSave} disabled={isSaving}>Save\u003C\u002Fbutton>\n}\n","",[30,31,32,52,59,66,72,78,84,89,102,139,167,172,186,199,208,220,232,238,244,253,265,271,277,282,316],"code",{"__ignoreMap":28},[33,34,37,41,45,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"svdQ7","import",[33,42,44],{"class":43},"ssxIu"," { useState } ",[33,46,47],{"class":39},"from",[33,49,51],{"class":50},"sJ6F3"," 'react'\n",[33,53,55],{"class":35,"line":54},2,[33,56,58],{"emptyLinePlaceholder":57},true,"\n",[33,60,62],{"class":35,"line":61},3,[33,63,65],{"class":64},"sdCPZ","\u002F\u002F Before React 18: only setState calls inside React event handlers were batched.\n",[33,67,69],{"class":35,"line":68},4,[33,70,71],{"class":64},"\u002F\u002F setState inside setTimeout, promises, or native listeners each triggered\n",[33,73,75],{"class":35,"line":74},5,[33,76,77],{"class":64},"\u002F\u002F a SEPARATE re-render. React 18 batches ALL setState calls regardless of\n",[33,79,81],{"class":35,"line":80},6,[33,82,83],{"class":64},"\u002F\u002F where they occur — a genuine behavioral change, not just an optimization.\n",[33,85,87],{"class":35,"line":86},7,[33,88,58],{"emptyLinePlaceholder":57},[33,90,92,95,99],{"class":35,"line":91},8,[33,93,94],{"class":39},"function",[33,96,98],{"class":97},"sIsaT"," ProductForm",[33,100,101],{"class":43},"() {\n",[33,103,105,108,111,115,118,121,124,127,130,133,136],{"class":35,"line":104},9,[33,106,107],{"class":39},"  const",[33,109,110],{"class":43}," [",[33,112,114],{"class":113},"snvgF","isSaving",[33,116,117],{"class":43},", ",[33,119,120],{"class":113},"setIsSaving",[33,122,123],{"class":43},"] ",[33,125,126],{"class":39},"=",[33,128,129],{"class":97}," useState",[33,131,132],{"class":43},"(",[33,134,135],{"class":113},"false",[33,137,138],{"class":43},")\n",[33,140,142,144,146,149,151,154,156,158,160,162,165],{"class":35,"line":141},10,[33,143,107],{"class":39},[33,145,110],{"class":43},[33,147,148],{"class":113},"error",[33,150,117],{"class":43},[33,152,153],{"class":113},"setError",[33,155,123],{"class":43},[33,157,126],{"class":39},[33,159,129],{"class":97},[33,161,132],{"class":43},[33,163,164],{"class":113},"null",[33,166,138],{"class":43},[33,168,170],{"class":35,"line":169},11,[33,171,58],{"emptyLinePlaceholder":57},[33,173,175,178,181,184],{"class":35,"line":174},12,[33,176,177],{"class":39},"  async",[33,179,180],{"class":39}," function",[33,182,183],{"class":97}," handleSave",[33,185,101],{"class":43},[33,187,189,192,194,197],{"class":35,"line":188},13,[33,190,191],{"class":97},"    setIsSaving",[33,193,132],{"class":43},[33,195,196],{"class":113},"true",[33,198,138],{"class":43},[33,200,202,205],{"class":35,"line":201},14,[33,203,204],{"class":39},"    try",[33,206,207],{"class":43}," {\n",[33,209,211,214,217],{"class":35,"line":210},15,[33,212,213],{"class":39},"      await",[33,215,216],{"class":97}," saveProduct",[33,218,219],{"class":43},"()\n",[33,221,223,226,229],{"class":35,"line":222},16,[33,224,225],{"class":43},"    } ",[33,227,228],{"class":39},"catch",[33,230,231],{"class":43}," (err) {\n",[33,233,235],{"class":35,"line":234},17,[33,236,237],{"class":64},"      \u002F\u002F React 18: both setState calls below are batched into ONE re-render.\n",[33,239,241],{"class":35,"line":240},18,[33,242,243],{"class":64},"      \u002F\u002F React 17: each triggered its own separate re-render (two renders).\n",[33,245,247,250],{"class":35,"line":246},19,[33,248,249],{"class":97},"      setError",[33,251,252],{"class":43},"(err)\n",[33,254,256,259,261,263],{"class":35,"line":255},20,[33,257,258],{"class":97},"      setIsSaving",[33,260,132],{"class":43},[33,262,135],{"class":113},[33,264,138],{"class":43},[33,266,268],{"class":35,"line":267},21,[33,269,270],{"class":43},"    }\n",[33,272,274],{"class":35,"line":273},22,[33,275,276],{"class":43},"  }\n",[33,278,280],{"class":35,"line":279},23,[33,281,58],{"emptyLinePlaceholder":57},[33,283,285,288,291,295,298,300,303,306,308,311,313],{"class":35,"line":284},24,[33,286,287],{"class":39},"  return",[33,289,290],{"class":43}," \u003C",[33,292,294],{"class":293},"sk71V","button",[33,296,297],{"class":97}," onClick",[33,299,126],{"class":39},[33,301,302],{"class":43},"{handleSave} ",[33,304,305],{"class":97},"disabled",[33,307,126],{"class":39},[33,309,310],{"class":43},"{isSaving}>Save\u003C\u002F",[33,312,294],{"class":293},[33,314,315],{"class":43},">\n",[33,317,319],{"class":35,"line":318},25,[33,320,321],{"class":43},"}\n",[323,324,326],"h3",{"id":325},"flushsync-the-escape-hatch","flushSync — The Escape Hatch",[19,328,330],{"filename":329,"language":22},"flush_sync.js",[24,331,333],{"className":26,"code":332,"language":22,"meta":28,"style":28},"import { flushSync } from 'react-dom'\n\n\u002F\u002F flushSync forces a synchronous, unbatched re-render — opting OUT of\n\u002F\u002F automatic batching. Use it ONLY when you need the DOM to update between\n\u002F\u002F two state changes (e.g., scrolling before a heavy render).\n\u002F\u002F DISCOURAGED by default — it defeats the batching improvements.\n\nfunction handleScrollAndRender(newScrollPos) {\n  flushSync(() => {\n    setScrollPosition(newScrollPos)  \u002F\u002F DOM updates immediately\n  })\n  \u002F\u002F Now the DOM reflects the new scroll position before this runs:\n  setHeavyContent(computeExpensiveContent())  \u002F\u002F separate render\n}\n",[30,334,335,347,351,356,361,366,371,375,391,404,415,420,425,441],{"__ignoreMap":28},[33,336,337,339,342,344],{"class":35,"line":36},[33,338,40],{"class":39},[33,340,341],{"class":43}," { flushSync } ",[33,343,47],{"class":39},[33,345,346],{"class":50}," 'react-dom'\n",[33,348,349],{"class":35,"line":54},[33,350,58],{"emptyLinePlaceholder":57},[33,352,353],{"class":35,"line":61},[33,354,355],{"class":64},"\u002F\u002F flushSync forces a synchronous, unbatched re-render — opting OUT of\n",[33,357,358],{"class":35,"line":68},[33,359,360],{"class":64},"\u002F\u002F automatic batching. Use it ONLY when you need the DOM to update between\n",[33,362,363],{"class":35,"line":74},[33,364,365],{"class":64},"\u002F\u002F two state changes (e.g., scrolling before a heavy render).\n",[33,367,368],{"class":35,"line":80},[33,369,370],{"class":64},"\u002F\u002F DISCOURAGED by default — it defeats the batching improvements.\n",[33,372,373],{"class":35,"line":86},[33,374,58],{"emptyLinePlaceholder":57},[33,376,377,379,382,384,388],{"class":35,"line":91},[33,378,94],{"class":39},[33,380,381],{"class":97}," handleScrollAndRender",[33,383,132],{"class":43},[33,385,387],{"class":386},"sCrzJ","newScrollPos",[33,389,390],{"class":43},") {\n",[33,392,393,396,399,402],{"class":35,"line":104},[33,394,395],{"class":97},"  flushSync",[33,397,398],{"class":43},"(() ",[33,400,401],{"class":39},"=>",[33,403,207],{"class":43},[33,405,406,409,412],{"class":35,"line":141},[33,407,408],{"class":97},"    setScrollPosition",[33,410,411],{"class":43},"(newScrollPos)  ",[33,413,414],{"class":64},"\u002F\u002F DOM updates immediately\n",[33,416,417],{"class":35,"line":169},[33,418,419],{"class":43},"  })\n",[33,421,422],{"class":35,"line":174},[33,423,424],{"class":64},"  \u002F\u002F Now the DOM reflects the new scroll position before this runs:\n",[33,426,427,430,432,435,438],{"class":35,"line":188},[33,428,429],{"class":97},"  setHeavyContent",[33,431,132],{"class":43},[33,433,434],{"class":97},"computeExpensiveContent",[33,436,437],{"class":43},"())  ",[33,439,440],{"class":64},"\u002F\u002F separate render\n",[33,442,443],{"class":35,"line":201},[33,444,321],{"class":43},[14,446,448],{"id":447},"starttransition-marking-updates-as-non-urgent","startTransition — Marking Updates as Non-Urgent",[19,450,452],{"filename":451,"language":22},"start_transition.js",[24,453,455],{"className":26,"code":454,"language":22,"meta":28,"style":28},"import { useState, startTransition } from 'react'\n\n\u002F\u002F Not every state update is equally urgent. Typing into an input needs to\n\u002F\u002F feel INSTANT — the character must appear the moment a key is pressed.\n\u002F\u002F Re-filtering a 10,000-row list based on that keystroke is less urgent.\n\u002F\u002F startTransition tells React to treat a state update as LOW PRIORITY,\n\u002F\u002F letting urgent updates (the input reflecting what was typed) interrupt\n\u002F\u002F and render first.\n\nfunction ProductSearch({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const [filtered, setFiltered] = useState(allProducts)\n\n  function handleChange(e) {\n    const value = e.target.value\n    setQuery(value)  \u002F\u002F URGENT: the input must visibly update immediately\n    startTransition(() => {\n      \u002F\u002F NON-URGENT: React can delay\u002Finterrupt this expensive filter if\n      \u002F\u002F something more urgent (the next keystroke) comes in first.\n      setFiltered(allProducts.filter(p =>\n        p.name.toLowerCase().includes(value.toLowerCase())\n      ))\n    })\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      \u003CProductGrid products={filtered} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F Without startTransition, typing quickly on a large catalog feels laggy —\n\u002F\u002F every keystroke triggers a synchronous, expensive re-filter before the\n\u002F\u002F browser is free to paint the next character. With startTransition, the\n\u002F\u002F input stays responsive because React prioritizes query's update first.\n",[30,456,457,468,472,477,482,487,492,497,502,506,522,549,572,576,591,605,616,627,632,637,656,678,683,688,692,696,704,715,739,755,765,771,776,781,787,793,799],{"__ignoreMap":28},[33,458,459,461,464,466],{"class":35,"line":36},[33,460,40],{"class":39},[33,462,463],{"class":43}," { useState, startTransition } ",[33,465,47],{"class":39},[33,467,51],{"class":50},[33,469,470],{"class":35,"line":54},[33,471,58],{"emptyLinePlaceholder":57},[33,473,474],{"class":35,"line":61},[33,475,476],{"class":64},"\u002F\u002F Not every state update is equally urgent. Typing into an input needs to\n",[33,478,479],{"class":35,"line":68},[33,480,481],{"class":64},"\u002F\u002F feel INSTANT — the character must appear the moment a key is pressed.\n",[33,483,484],{"class":35,"line":74},[33,485,486],{"class":64},"\u002F\u002F Re-filtering a 10,000-row list based on that keystroke is less urgent.\n",[33,488,489],{"class":35,"line":80},[33,490,491],{"class":64},"\u002F\u002F startTransition tells React to treat a state update as LOW PRIORITY,\n",[33,493,494],{"class":35,"line":86},[33,495,496],{"class":64},"\u002F\u002F letting urgent updates (the input reflecting what was typed) interrupt\n",[33,498,499],{"class":35,"line":91},[33,500,501],{"class":64},"\u002F\u002F and render first.\n",[33,503,504],{"class":35,"line":104},[33,505,58],{"emptyLinePlaceholder":57},[33,507,508,510,513,516,519],{"class":35,"line":141},[33,509,94],{"class":39},[33,511,512],{"class":97}," ProductSearch",[33,514,515],{"class":43},"({ ",[33,517,518],{"class":386},"allProducts",[33,520,521],{"class":43}," }) {\n",[33,523,524,526,528,531,533,536,538,540,542,544,547],{"class":35,"line":169},[33,525,107],{"class":39},[33,527,110],{"class":43},[33,529,530],{"class":113},"query",[33,532,117],{"class":43},[33,534,535],{"class":113},"setQuery",[33,537,123],{"class":43},[33,539,126],{"class":39},[33,541,129],{"class":97},[33,543,132],{"class":43},[33,545,546],{"class":50},"''",[33,548,138],{"class":43},[33,550,551,553,555,558,560,563,565,567,569],{"class":35,"line":174},[33,552,107],{"class":39},[33,554,110],{"class":43},[33,556,557],{"class":113},"filtered",[33,559,117],{"class":43},[33,561,562],{"class":113},"setFiltered",[33,564,123],{"class":43},[33,566,126],{"class":39},[33,568,129],{"class":97},[33,570,571],{"class":43},"(allProducts)\n",[33,573,574],{"class":35,"line":188},[33,575,58],{"emptyLinePlaceholder":57},[33,577,578,581,584,586,589],{"class":35,"line":201},[33,579,580],{"class":39},"  function",[33,582,583],{"class":97}," handleChange",[33,585,132],{"class":43},[33,587,588],{"class":386},"e",[33,590,390],{"class":43},[33,592,593,596,599,602],{"class":35,"line":210},[33,594,595],{"class":39},"    const",[33,597,598],{"class":113}," value",[33,600,601],{"class":39}," =",[33,603,604],{"class":43}," e.target.value\n",[33,606,607,610,613],{"class":35,"line":222},[33,608,609],{"class":97},"    setQuery",[33,611,612],{"class":43},"(value)  ",[33,614,615],{"class":64},"\u002F\u002F URGENT: the input must visibly update immediately\n",[33,617,618,621,623,625],{"class":35,"line":234},[33,619,620],{"class":97},"    startTransition",[33,622,398],{"class":43},[33,624,401],{"class":39},[33,626,207],{"class":43},[33,628,629],{"class":35,"line":240},[33,630,631],{"class":64},"      \u002F\u002F NON-URGENT: React can delay\u002Finterrupt this expensive filter if\n",[33,633,634],{"class":35,"line":246},[33,635,636],{"class":64},"      \u002F\u002F something more urgent (the next keystroke) comes in first.\n",[33,638,639,642,645,648,650,653],{"class":35,"line":255},[33,640,641],{"class":97},"      setFiltered",[33,643,644],{"class":43},"(allProducts.",[33,646,647],{"class":97},"filter",[33,649,132],{"class":43},[33,651,652],{"class":386},"p",[33,654,655],{"class":39}," =>\n",[33,657,658,661,664,667,670,673,675],{"class":35,"line":267},[33,659,660],{"class":43},"        p.name.",[33,662,663],{"class":97},"toLowerCase",[33,665,666],{"class":43},"().",[33,668,669],{"class":97},"includes",[33,671,672],{"class":43},"(value.",[33,674,663],{"class":97},[33,676,677],{"class":43},"())\n",[33,679,680],{"class":35,"line":273},[33,681,682],{"class":43},"      ))\n",[33,684,685],{"class":35,"line":279},[33,686,687],{"class":43},"    })\n",[33,689,690],{"class":35,"line":284},[33,691,276],{"class":43},[33,693,694],{"class":35,"line":318},[33,695,58],{"emptyLinePlaceholder":57},[33,697,699,701],{"class":35,"line":698},26,[33,700,287],{"class":39},[33,702,703],{"class":43}," (\n",[33,705,707,710,713],{"class":35,"line":706},27,[33,708,709],{"class":43},"    \u003C",[33,711,712],{"class":293},"div",[33,714,315],{"class":43},[33,716,718,721,724,726,728,731,734,736],{"class":35,"line":717},28,[33,719,720],{"class":43},"      \u003C",[33,722,723],{"class":293},"input",[33,725,598],{"class":97},[33,727,126],{"class":39},[33,729,730],{"class":43},"{query} ",[33,732,733],{"class":97},"onChange",[33,735,126],{"class":39},[33,737,738],{"class":43},"{handleChange} \u002F>\n",[33,740,742,744,747,750,752],{"class":35,"line":741},29,[33,743,720],{"class":43},[33,745,746],{"class":113},"ProductGrid",[33,748,749],{"class":97}," products",[33,751,126],{"class":39},[33,753,754],{"class":43},"{filtered} \u002F>\n",[33,756,758,761,763],{"class":35,"line":757},30,[33,759,760],{"class":43},"    \u003C\u002F",[33,762,712],{"class":293},[33,764,315],{"class":43},[33,766,768],{"class":35,"line":767},31,[33,769,770],{"class":43},"  )\n",[33,772,774],{"class":35,"line":773},32,[33,775,321],{"class":43},[33,777,779],{"class":35,"line":778},33,[33,780,58],{"emptyLinePlaceholder":57},[33,782,784],{"class":35,"line":783},34,[33,785,786],{"class":64},"\u002F\u002F Without startTransition, typing quickly on a large catalog feels laggy —\n",[33,788,790],{"class":35,"line":789},35,[33,791,792],{"class":64},"\u002F\u002F every keystroke triggers a synchronous, expensive re-filter before the\n",[33,794,796],{"class":35,"line":795},36,[33,797,798],{"class":64},"\u002F\u002F browser is free to paint the next character. With startTransition, the\n",[33,800,802],{"class":35,"line":801},37,[33,803,804],{"class":64},"\u002F\u002F input stays responsive because React prioritizes query's update first.\n",[323,806,808],{"id":807},"starttransition-must-be-synchronous","startTransition Must Be Synchronous",[19,810,812],{"filename":811,"language":22},"transition_sync.js",[24,813,815],{"className":26,"code":814,"language":22,"meta":28,"style":28},"\u002F\u002F CORRECT: the setState call is synchronous within the transition callback.\nstartTransition(() => {\n  setFiltered(expensiveFilter(allProducts, value))  \u002F\u002F sync — marked low-priority\n})\n\n\u002F\u002F WRONG: async callback — only the SYNCHRONOUS portion is tracked as part\n\u002F\u002F of the transition. Anything after `await` runs OUTSIDE the transition's\n\u002F\u002F low-priority marking, defeating the purpose.\nstartTransition(async () => {\n  setQuery(value)  \u002F\u002F this IS in the transition (sync, before await)\n  const results = await runExpensiveFilter(allProducts, value)\n  setFiltered(results)  \u002F\u002F this is NOT in the transition (after await)\n})\n\n\u002F\u002F FIX: keep urgent updates OUTSIDE the transition, and keep the\n\u002F\u002F transition callback synchronous:\nsetQuery(value)  \u002F\u002F urgent — outside transition\nstartTransition(() => {\n  setFiltered(syncFilter(allProducts, value))  \u002F\u002F non-urgent, synchronous\n})\n\n\u002F\u002F If you need async work, perform it outside, then mark only the\n\u002F\u002F state update as a transition:\nsetQuery(value)\nrunExpensiveFilter(allProducts, value).then(results => {\n  startTransition(() => setFiltered(results))\n})\n",[30,816,817,822,833,849,854,858,863,868,873,889,899,917,927,931,935,940,945,954,964,978,982,986,991,996,1003,1024,1039],{"__ignoreMap":28},[33,818,819],{"class":35,"line":36},[33,820,821],{"class":64},"\u002F\u002F CORRECT: the setState call is synchronous within the transition callback.\n",[33,823,824,827,829,831],{"class":35,"line":54},[33,825,826],{"class":97},"startTransition",[33,828,398],{"class":43},[33,830,401],{"class":39},[33,832,207],{"class":43},[33,834,835,838,840,843,846],{"class":35,"line":61},[33,836,837],{"class":97},"  setFiltered",[33,839,132],{"class":43},[33,841,842],{"class":97},"expensiveFilter",[33,844,845],{"class":43},"(allProducts, value))  ",[33,847,848],{"class":64},"\u002F\u002F sync — marked low-priority\n",[33,850,851],{"class":35,"line":68},[33,852,853],{"class":43},"})\n",[33,855,856],{"class":35,"line":74},[33,857,58],{"emptyLinePlaceholder":57},[33,859,860],{"class":35,"line":80},[33,861,862],{"class":64},"\u002F\u002F WRONG: async callback — only the SYNCHRONOUS portion is tracked as part\n",[33,864,865],{"class":35,"line":86},[33,866,867],{"class":64},"\u002F\u002F of the transition. Anything after `await` runs OUTSIDE the transition's\n",[33,869,870],{"class":35,"line":91},[33,871,872],{"class":64},"\u002F\u002F low-priority marking, defeating the purpose.\n",[33,874,875,877,879,882,885,887],{"class":35,"line":104},[33,876,826],{"class":97},[33,878,132],{"class":43},[33,880,881],{"class":39},"async",[33,883,884],{"class":43}," () ",[33,886,401],{"class":39},[33,888,207],{"class":43},[33,890,891,894,896],{"class":35,"line":141},[33,892,893],{"class":97},"  setQuery",[33,895,612],{"class":43},[33,897,898],{"class":64},"\u002F\u002F this IS in the transition (sync, before await)\n",[33,900,901,903,906,908,911,914],{"class":35,"line":169},[33,902,107],{"class":39},[33,904,905],{"class":113}," results",[33,907,601],{"class":39},[33,909,910],{"class":39}," await",[33,912,913],{"class":97}," runExpensiveFilter",[33,915,916],{"class":43},"(allProducts, value)\n",[33,918,919,921,924],{"class":35,"line":174},[33,920,837],{"class":97},[33,922,923],{"class":43},"(results)  ",[33,925,926],{"class":64},"\u002F\u002F this is NOT in the transition (after await)\n",[33,928,929],{"class":35,"line":188},[33,930,853],{"class":43},[33,932,933],{"class":35,"line":201},[33,934,58],{"emptyLinePlaceholder":57},[33,936,937],{"class":35,"line":210},[33,938,939],{"class":64},"\u002F\u002F FIX: keep urgent updates OUTSIDE the transition, and keep the\n",[33,941,942],{"class":35,"line":222},[33,943,944],{"class":64},"\u002F\u002F transition callback synchronous:\n",[33,946,947,949,951],{"class":35,"line":234},[33,948,535],{"class":97},[33,950,612],{"class":43},[33,952,953],{"class":64},"\u002F\u002F urgent — outside transition\n",[33,955,956,958,960,962],{"class":35,"line":240},[33,957,826],{"class":97},[33,959,398],{"class":43},[33,961,401],{"class":39},[33,963,207],{"class":43},[33,965,966,968,970,973,975],{"class":35,"line":246},[33,967,837],{"class":97},[33,969,132],{"class":43},[33,971,972],{"class":97},"syncFilter",[33,974,845],{"class":43},[33,976,977],{"class":64},"\u002F\u002F non-urgent, synchronous\n",[33,979,980],{"class":35,"line":255},[33,981,853],{"class":43},[33,983,984],{"class":35,"line":267},[33,985,58],{"emptyLinePlaceholder":57},[33,987,988],{"class":35,"line":273},[33,989,990],{"class":64},"\u002F\u002F If you need async work, perform it outside, then mark only the\n",[33,992,993],{"class":35,"line":279},[33,994,995],{"class":64},"\u002F\u002F state update as a transition:\n",[33,997,998,1000],{"class":35,"line":284},[33,999,535],{"class":97},[33,1001,1002],{"class":43},"(value)\n",[33,1004,1005,1008,1011,1014,1016,1019,1022],{"class":35,"line":318},[33,1006,1007],{"class":97},"runExpensiveFilter",[33,1009,1010],{"class":43},"(allProducts, value).",[33,1012,1013],{"class":97},"then",[33,1015,132],{"class":43},[33,1017,1018],{"class":386},"results",[33,1020,1021],{"class":39}," =>",[33,1023,207],{"class":43},[33,1025,1026,1029,1031,1033,1036],{"class":35,"line":698},[33,1027,1028],{"class":97},"  startTransition",[33,1030,398],{"class":43},[33,1032,401],{"class":39},[33,1034,1035],{"class":97}," setFiltered",[33,1037,1038],{"class":43},"(results))\n",[33,1040,1041],{"class":35,"line":706},[33,1042,853],{"class":43},[14,1044,1046],{"id":1045},"usetransition-tracking-pending-state","useTransition — Tracking Pending State",[19,1048,1050],{"filename":1049,"language":22},"use_transition.js",[24,1051,1053],{"className":26,"code":1052,"language":22,"meta":28,"style":28},"import { useState, useTransition } from 'react'\n\n\u002F\u002F useTransition pairs startTransition's functionality with an isPending\n\u002F\u002F boolean, letting the UI show that a low-priority update is in flight.\n\nfunction ProductSearch({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const [filtered, setFiltered] = useState(allProducts)\n  const [isPending, startTransition] = useTransition()\n\n  function handleChange(e) {\n    const value = e.target.value\n    setQuery(value)  \u002F\u002F urgent — input updates immediately\n    startTransition(() => {\n      setFiltered(allProducts.filter(p =>\n        p.name.toLowerCase().includes(value.toLowerCase())\n      ))\n    })\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      {\u002F* isPending is true between calling startTransition and the\n          transition's update actually committing — dim the grid slightly\n          to give visual feedback that a filter is in progress *\u002F}\n      \u003Cdiv style={{ opacity: isPending ? 0.6 : 1 }}>\n        \u003CProductGrid products={filtered} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F isPending is a better UX signal than a manually-tracked isFiltering boolean\n\u002F\u002F — it's wired directly into React's own scheduling, accurately reflecting\n\u002F\u002F exactly when the deferred work is actually in flight versus already committed.\n",[30,1054,1055,1066,1070,1075,1080,1084,1096,1120,1140,1162,1166,1178,1188,1197,1207,1221,1237,1241,1245,1249,1253,1259,1267,1285,1293,1298,1305,1334,1347,1356,1364,1368,1372,1376,1381,1386],{"__ignoreMap":28},[33,1056,1057,1059,1062,1064],{"class":35,"line":36},[33,1058,40],{"class":39},[33,1060,1061],{"class":43}," { useState, useTransition } ",[33,1063,47],{"class":39},[33,1065,51],{"class":50},[33,1067,1068],{"class":35,"line":54},[33,1069,58],{"emptyLinePlaceholder":57},[33,1071,1072],{"class":35,"line":61},[33,1073,1074],{"class":64},"\u002F\u002F useTransition pairs startTransition's functionality with an isPending\n",[33,1076,1077],{"class":35,"line":68},[33,1078,1079],{"class":64},"\u002F\u002F boolean, letting the UI show that a low-priority update is in flight.\n",[33,1081,1082],{"class":35,"line":74},[33,1083,58],{"emptyLinePlaceholder":57},[33,1085,1086,1088,1090,1092,1094],{"class":35,"line":80},[33,1087,94],{"class":39},[33,1089,512],{"class":97},[33,1091,515],{"class":43},[33,1093,518],{"class":386},[33,1095,521],{"class":43},[33,1097,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118],{"class":35,"line":86},[33,1099,107],{"class":39},[33,1101,110],{"class":43},[33,1103,530],{"class":113},[33,1105,117],{"class":43},[33,1107,535],{"class":113},[33,1109,123],{"class":43},[33,1111,126],{"class":39},[33,1113,129],{"class":97},[33,1115,132],{"class":43},[33,1117,546],{"class":50},[33,1119,138],{"class":43},[33,1121,1122,1124,1126,1128,1130,1132,1134,1136,1138],{"class":35,"line":91},[33,1123,107],{"class":39},[33,1125,110],{"class":43},[33,1127,557],{"class":113},[33,1129,117],{"class":43},[33,1131,562],{"class":113},[33,1133,123],{"class":43},[33,1135,126],{"class":39},[33,1137,129],{"class":97},[33,1139,571],{"class":43},[33,1141,1142,1144,1146,1149,1151,1153,1155,1157,1160],{"class":35,"line":104},[33,1143,107],{"class":39},[33,1145,110],{"class":43},[33,1147,1148],{"class":113},"isPending",[33,1150,117],{"class":43},[33,1152,826],{"class":113},[33,1154,123],{"class":43},[33,1156,126],{"class":39},[33,1158,1159],{"class":97}," useTransition",[33,1161,219],{"class":43},[33,1163,1164],{"class":35,"line":141},[33,1165,58],{"emptyLinePlaceholder":57},[33,1167,1168,1170,1172,1174,1176],{"class":35,"line":169},[33,1169,580],{"class":39},[33,1171,583],{"class":97},[33,1173,132],{"class":43},[33,1175,588],{"class":386},[33,1177,390],{"class":43},[33,1179,1180,1182,1184,1186],{"class":35,"line":174},[33,1181,595],{"class":39},[33,1183,598],{"class":113},[33,1185,601],{"class":39},[33,1187,604],{"class":43},[33,1189,1190,1192,1194],{"class":35,"line":188},[33,1191,609],{"class":97},[33,1193,612],{"class":43},[33,1195,1196],{"class":64},"\u002F\u002F urgent — input updates immediately\n",[33,1198,1199,1201,1203,1205],{"class":35,"line":201},[33,1200,620],{"class":97},[33,1202,398],{"class":43},[33,1204,401],{"class":39},[33,1206,207],{"class":43},[33,1208,1209,1211,1213,1215,1217,1219],{"class":35,"line":210},[33,1210,641],{"class":97},[33,1212,644],{"class":43},[33,1214,647],{"class":97},[33,1216,132],{"class":43},[33,1218,652],{"class":386},[33,1220,655],{"class":39},[33,1222,1223,1225,1227,1229,1231,1233,1235],{"class":35,"line":222},[33,1224,660],{"class":43},[33,1226,663],{"class":97},[33,1228,666],{"class":43},[33,1230,669],{"class":97},[33,1232,672],{"class":43},[33,1234,663],{"class":97},[33,1236,677],{"class":43},[33,1238,1239],{"class":35,"line":234},[33,1240,682],{"class":43},[33,1242,1243],{"class":35,"line":240},[33,1244,687],{"class":43},[33,1246,1247],{"class":35,"line":246},[33,1248,276],{"class":43},[33,1250,1251],{"class":35,"line":255},[33,1252,58],{"emptyLinePlaceholder":57},[33,1254,1255,1257],{"class":35,"line":267},[33,1256,287],{"class":39},[33,1258,703],{"class":43},[33,1260,1261,1263,1265],{"class":35,"line":273},[33,1262,709],{"class":43},[33,1264,712],{"class":293},[33,1266,315],{"class":43},[33,1268,1269,1271,1273,1275,1277,1279,1281,1283],{"class":35,"line":279},[33,1270,720],{"class":43},[33,1272,723],{"class":293},[33,1274,598],{"class":97},[33,1276,126],{"class":39},[33,1278,730],{"class":43},[33,1280,733],{"class":97},[33,1282,126],{"class":39},[33,1284,738],{"class":43},[33,1286,1287,1290],{"class":35,"line":284},[33,1288,1289],{"class":43},"      {",[33,1291,1292],{"class":64},"\u002F* isPending is true between calling startTransition and the\n",[33,1294,1295],{"class":35,"line":318},[33,1296,1297],{"class":64},"          transition's update actually committing — dim the grid slightly\n",[33,1299,1300,1303],{"class":35,"line":698},[33,1301,1302],{"class":64},"          to give visual feedback that a filter is in progress *\u002F",[33,1304,321],{"class":43},[33,1306,1307,1309,1311,1314,1316,1319,1322,1325,1328,1331],{"class":35,"line":706},[33,1308,720],{"class":43},[33,1310,712],{"class":293},[33,1312,1313],{"class":97}," style",[33,1315,126],{"class":39},[33,1317,1318],{"class":43},"{{ opacity: isPending ",[33,1320,1321],{"class":39},"?",[33,1323,1324],{"class":113}," 0.6",[33,1326,1327],{"class":39}," :",[33,1329,1330],{"class":113}," 1",[33,1332,1333],{"class":43}," }}>\n",[33,1335,1336,1339,1341,1343,1345],{"class":35,"line":717},[33,1337,1338],{"class":43},"        \u003C",[33,1340,746],{"class":113},[33,1342,749],{"class":97},[33,1344,126],{"class":39},[33,1346,754],{"class":43},[33,1348,1349,1352,1354],{"class":35,"line":741},[33,1350,1351],{"class":43},"      \u003C\u002F",[33,1353,712],{"class":293},[33,1355,315],{"class":43},[33,1357,1358,1360,1362],{"class":35,"line":757},[33,1359,760],{"class":43},[33,1361,712],{"class":293},[33,1363,315],{"class":43},[33,1365,1366],{"class":35,"line":767},[33,1367,770],{"class":43},[33,1369,1370],{"class":35,"line":773},[33,1371,321],{"class":43},[33,1373,1374],{"class":35,"line":778},[33,1375,58],{"emptyLinePlaceholder":57},[33,1377,1378],{"class":35,"line":783},[33,1379,1380],{"class":64},"\u002F\u002F isPending is a better UX signal than a manually-tracked isFiltering boolean\n",[33,1382,1383],{"class":35,"line":789},[33,1384,1385],{"class":64},"\u002F\u002F — it's wired directly into React's own scheduling, accurately reflecting\n",[33,1387,1388],{"class":35,"line":795},[33,1389,1390],{"class":64},"\u002F\u002F exactly when the deferred work is actually in flight versus already committed.\n",[323,1392,1394],{"id":1393},"usetransition-for-tab-switching","useTransition for Tab Switching",[19,1396,1398],{"filename":1397,"language":22},"transition_tabs.js",[24,1399,1401],{"className":26,"code":1400,"language":22,"meta":28,"style":28},"import { useState, useTransition, lazy, Suspense } from 'react'\n\n\u002F\u002F Tab switching to a heavy component can stutter if the new tab's render\n\u002F\u002F is expensive. useTransition keeps the current tab interactive while the\n\u002F\u002F new one renders in the background.\n\nconst HeavyTab = lazy(() => import('.\u002FHeavyTab'))\nconst SettingsTab = lazy(() => import('.\u002FSettingsTab'))\n\nfunction TabContainer() {\n  const [tab, setTab] = useState('heavy')\n  const [isPending, startTransition] = useTransition()\n\n  function selectTab(nextTab) {\n    startTransition(() => setTab(nextTab))  \u002F\u002F non-urgent tab switch\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003Cbutton onClick={() => selectTab('heavy')} disabled={isPending}>Heavy\u003C\u002Fbutton>\n        \u003Cbutton onClick={() => selectTab('settings')} disabled={isPending}>Settings\u003C\u002Fbutton>\n      \u003C\u002Fnav>\n      {\u002F* Show the old tab while the new one renders — no blank flash *\u002F}\n      \u003CSuspense fallback={\u003CSpinner \u002F>}>\n        {tab === 'heavy' ? \u003CHeavyTab \u002F> : \u003CSettingsTab \u002F>}\n      \u003C\u002FSuspense>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,1402,1403,1414,1418,1423,1428,1433,1437,1465,1489,1493,1502,1529,1549,1553,1567,1584,1588,1592,1598,1606,1615,1650,1684,1692,1701,1722,1755,1763,1771,1775],{"__ignoreMap":28},[33,1404,1405,1407,1410,1412],{"class":35,"line":36},[33,1406,40],{"class":39},[33,1408,1409],{"class":43}," { useState, useTransition, lazy, Suspense } ",[33,1411,47],{"class":39},[33,1413,51],{"class":50},[33,1415,1416],{"class":35,"line":54},[33,1417,58],{"emptyLinePlaceholder":57},[33,1419,1420],{"class":35,"line":61},[33,1421,1422],{"class":64},"\u002F\u002F Tab switching to a heavy component can stutter if the new tab's render\n",[33,1424,1425],{"class":35,"line":68},[33,1426,1427],{"class":64},"\u002F\u002F is expensive. useTransition keeps the current tab interactive while the\n",[33,1429,1430],{"class":35,"line":74},[33,1431,1432],{"class":64},"\u002F\u002F new one renders in the background.\n",[33,1434,1435],{"class":35,"line":80},[33,1436,58],{"emptyLinePlaceholder":57},[33,1438,1439,1442,1445,1447,1450,1452,1454,1457,1459,1462],{"class":35,"line":86},[33,1440,1441],{"class":39},"const",[33,1443,1444],{"class":113}," HeavyTab",[33,1446,601],{"class":39},[33,1448,1449],{"class":97}," lazy",[33,1451,398],{"class":43},[33,1453,401],{"class":39},[33,1455,1456],{"class":39}," import",[33,1458,132],{"class":43},[33,1460,1461],{"class":50},"'.\u002FHeavyTab'",[33,1463,1464],{"class":43},"))\n",[33,1466,1467,1469,1472,1474,1476,1478,1480,1482,1484,1487],{"class":35,"line":91},[33,1468,1441],{"class":39},[33,1470,1471],{"class":113}," SettingsTab",[33,1473,601],{"class":39},[33,1475,1449],{"class":97},[33,1477,398],{"class":43},[33,1479,401],{"class":39},[33,1481,1456],{"class":39},[33,1483,132],{"class":43},[33,1485,1486],{"class":50},"'.\u002FSettingsTab'",[33,1488,1464],{"class":43},[33,1490,1491],{"class":35,"line":104},[33,1492,58],{"emptyLinePlaceholder":57},[33,1494,1495,1497,1500],{"class":35,"line":141},[33,1496,94],{"class":39},[33,1498,1499],{"class":97}," TabContainer",[33,1501,101],{"class":43},[33,1503,1504,1506,1508,1511,1513,1516,1518,1520,1522,1524,1527],{"class":35,"line":169},[33,1505,107],{"class":39},[33,1507,110],{"class":43},[33,1509,1510],{"class":113},"tab",[33,1512,117],{"class":43},[33,1514,1515],{"class":113},"setTab",[33,1517,123],{"class":43},[33,1519,126],{"class":39},[33,1521,129],{"class":97},[33,1523,132],{"class":43},[33,1525,1526],{"class":50},"'heavy'",[33,1528,138],{"class":43},[33,1530,1531,1533,1535,1537,1539,1541,1543,1545,1547],{"class":35,"line":174},[33,1532,107],{"class":39},[33,1534,110],{"class":43},[33,1536,1148],{"class":113},[33,1538,117],{"class":43},[33,1540,826],{"class":113},[33,1542,123],{"class":43},[33,1544,126],{"class":39},[33,1546,1159],{"class":97},[33,1548,219],{"class":43},[33,1550,1551],{"class":35,"line":188},[33,1552,58],{"emptyLinePlaceholder":57},[33,1554,1555,1557,1560,1562,1565],{"class":35,"line":201},[33,1556,580],{"class":39},[33,1558,1559],{"class":97}," selectTab",[33,1561,132],{"class":43},[33,1563,1564],{"class":386},"nextTab",[33,1566,390],{"class":43},[33,1568,1569,1571,1573,1575,1578,1581],{"class":35,"line":210},[33,1570,620],{"class":97},[33,1572,398],{"class":43},[33,1574,401],{"class":39},[33,1576,1577],{"class":97}," setTab",[33,1579,1580],{"class":43},"(nextTab))  ",[33,1582,1583],{"class":64},"\u002F\u002F non-urgent tab switch\n",[33,1585,1586],{"class":35,"line":222},[33,1587,276],{"class":43},[33,1589,1590],{"class":35,"line":234},[33,1591,58],{"emptyLinePlaceholder":57},[33,1593,1594,1596],{"class":35,"line":240},[33,1595,287],{"class":39},[33,1597,703],{"class":43},[33,1599,1600,1602,1604],{"class":35,"line":246},[33,1601,709],{"class":43},[33,1603,712],{"class":293},[33,1605,315],{"class":43},[33,1607,1608,1610,1613],{"class":35,"line":255},[33,1609,720],{"class":43},[33,1611,1612],{"class":293},"nav",[33,1614,315],{"class":43},[33,1616,1617,1619,1621,1623,1625,1628,1630,1632,1634,1636,1639,1641,1643,1646,1648],{"class":35,"line":267},[33,1618,1338],{"class":43},[33,1620,294],{"class":293},[33,1622,297],{"class":97},[33,1624,126],{"class":39},[33,1626,1627],{"class":43},"{() ",[33,1629,401],{"class":39},[33,1631,1559],{"class":97},[33,1633,132],{"class":43},[33,1635,1526],{"class":50},[33,1637,1638],{"class":43},")} ",[33,1640,305],{"class":97},[33,1642,126],{"class":39},[33,1644,1645],{"class":43},"{isPending}>Heavy\u003C\u002F",[33,1647,294],{"class":293},[33,1649,315],{"class":43},[33,1651,1652,1654,1656,1658,1660,1662,1664,1666,1668,1671,1673,1675,1677,1680,1682],{"class":35,"line":273},[33,1653,1338],{"class":43},[33,1655,294],{"class":293},[33,1657,297],{"class":97},[33,1659,126],{"class":39},[33,1661,1627],{"class":43},[33,1663,401],{"class":39},[33,1665,1559],{"class":97},[33,1667,132],{"class":43},[33,1669,1670],{"class":50},"'settings'",[33,1672,1638],{"class":43},[33,1674,305],{"class":97},[33,1676,126],{"class":39},[33,1678,1679],{"class":43},"{isPending}>Settings\u003C\u002F",[33,1681,294],{"class":293},[33,1683,315],{"class":43},[33,1685,1686,1688,1690],{"class":35,"line":279},[33,1687,1351],{"class":43},[33,1689,1612],{"class":293},[33,1691,315],{"class":43},[33,1693,1694,1696,1699],{"class":35,"line":284},[33,1695,1289],{"class":43},[33,1697,1698],{"class":64},"\u002F* Show the old tab while the new one renders — no blank flash *\u002F",[33,1700,321],{"class":43},[33,1702,1703,1705,1708,1711,1713,1716,1719],{"class":35,"line":318},[33,1704,720],{"class":43},[33,1706,1707],{"class":113},"Suspense",[33,1709,1710],{"class":97}," fallback",[33,1712,126],{"class":39},[33,1714,1715],{"class":43},"{\u003C",[33,1717,1718],{"class":113},"Spinner",[33,1720,1721],{"class":43}," \u002F>}>\n",[33,1723,1724,1727,1730,1733,1736,1738,1741,1744,1747,1749,1752],{"class":35,"line":698},[33,1725,1726],{"class":43},"        {tab ",[33,1728,1729],{"class":39},"===",[33,1731,1732],{"class":50}," 'heavy'",[33,1734,1735],{"class":39}," ?",[33,1737,290],{"class":43},[33,1739,1740],{"class":113},"HeavyTab",[33,1742,1743],{"class":43}," \u002F> ",[33,1745,1746],{"class":39},":",[33,1748,290],{"class":43},[33,1750,1751],{"class":113},"SettingsTab",[33,1753,1754],{"class":43}," \u002F>}\n",[33,1756,1757,1759,1761],{"class":35,"line":706},[33,1758,1351],{"class":43},[33,1760,1707],{"class":113},[33,1762,315],{"class":43},[33,1764,1765,1767,1769],{"class":35,"line":717},[33,1766,760],{"class":43},[33,1768,712],{"class":293},[33,1770,315],{"class":43},[33,1772,1773],{"class":35,"line":741},[33,1774,770],{"class":43},[33,1776,1777],{"class":35,"line":757},[33,1778,321],{"class":43},[14,1780,1782],{"id":1781},"usedeferredvalue-deferring-a-value","useDeferredValue — Deferring a Value",[19,1784,1786],{"filename":1785,"language":22},"use_deferred_value.js",[24,1787,1789],{"className":26,"code":1788,"language":22,"meta":28,"style":28},"import { useState, useDeferredValue, useMemo } from 'react'\n\n\u002F\u002F useDeferredValue solves the same problem as startTransition from the\n\u002F\u002F OPPOSITE direction. Instead of marking the STATE UPDATE as low-priority\n\u002F\u002F at the point it's set, it lets a component request a \"lagging\" version\n\u002F\u002F of an already-existing value — useful when the value comes from a prop\n\u002F\u002F or another hook you don't control the setState call for.\n\nfunction ProductSearch({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const deferredQuery = useDeferredValue(query)\n\n  \u002F\u002F ProductGrid re-renders using deferredQuery, which \"lags behind\" query\n  \u002F\u002F during rapid typing — React prioritizes keeping the input itself\n  \u002F\u002F (bound to query, not deferredQuery) responsive.\n  const filtered = useMemo(\n    () => allProducts.filter(p =>\n      p.name.toLowerCase().includes(deferredQuery.toLowerCase())\n    ),\n    [allProducts, deferredQuery]\n  )\n\n  const isStale = query !== deferredQuery  \u002F\u002F true while a deferred update is in flight\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n      \u003Cdiv style={{ opacity: isStale ? 0.6 : 1 }}>\n        \u003CProductGrid products={filtered} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F PRACTICAL DIFFERENCE from startTransition:\n\u002F\u002F - startTransition: you OWN the setState call -> wrap it in startTransition\n\u002F\u002F - useDeferredValue: you RECEIVE a value (prop, library hook) -> defer it\n\u002F\u002F You can't wrap someone else's state setter in startTransition, but you\n\u002F\u002F CAN defer any value you have access to.\n",[30,1790,1791,1802,1806,1811,1816,1821,1826,1831,1835,1847,1871,1886,1890,1895,1900,1905,1920,1938,1956,1961,1966,1970,1974,1995,1999,2005,2013,2042,2065,2077,2085,2093,2097,2101,2105,2110,2115,2120,2126],{"__ignoreMap":28},[33,1792,1793,1795,1798,1800],{"class":35,"line":36},[33,1794,40],{"class":39},[33,1796,1797],{"class":43}," { useState, useDeferredValue, useMemo } ",[33,1799,47],{"class":39},[33,1801,51],{"class":50},[33,1803,1804],{"class":35,"line":54},[33,1805,58],{"emptyLinePlaceholder":57},[33,1807,1808],{"class":35,"line":61},[33,1809,1810],{"class":64},"\u002F\u002F useDeferredValue solves the same problem as startTransition from the\n",[33,1812,1813],{"class":35,"line":68},[33,1814,1815],{"class":64},"\u002F\u002F OPPOSITE direction. Instead of marking the STATE UPDATE as low-priority\n",[33,1817,1818],{"class":35,"line":74},[33,1819,1820],{"class":64},"\u002F\u002F at the point it's set, it lets a component request a \"lagging\" version\n",[33,1822,1823],{"class":35,"line":80},[33,1824,1825],{"class":64},"\u002F\u002F of an already-existing value — useful when the value comes from a prop\n",[33,1827,1828],{"class":35,"line":86},[33,1829,1830],{"class":64},"\u002F\u002F or another hook you don't control the setState call for.\n",[33,1832,1833],{"class":35,"line":91},[33,1834,58],{"emptyLinePlaceholder":57},[33,1836,1837,1839,1841,1843,1845],{"class":35,"line":104},[33,1838,94],{"class":39},[33,1840,512],{"class":97},[33,1842,515],{"class":43},[33,1844,518],{"class":386},[33,1846,521],{"class":43},[33,1848,1849,1851,1853,1855,1857,1859,1861,1863,1865,1867,1869],{"class":35,"line":141},[33,1850,107],{"class":39},[33,1852,110],{"class":43},[33,1854,530],{"class":113},[33,1856,117],{"class":43},[33,1858,535],{"class":113},[33,1860,123],{"class":43},[33,1862,126],{"class":39},[33,1864,129],{"class":97},[33,1866,132],{"class":43},[33,1868,546],{"class":50},[33,1870,138],{"class":43},[33,1872,1873,1875,1878,1880,1883],{"class":35,"line":169},[33,1874,107],{"class":39},[33,1876,1877],{"class":113}," deferredQuery",[33,1879,601],{"class":39},[33,1881,1882],{"class":97}," useDeferredValue",[33,1884,1885],{"class":43},"(query)\n",[33,1887,1888],{"class":35,"line":174},[33,1889,58],{"emptyLinePlaceholder":57},[33,1891,1892],{"class":35,"line":188},[33,1893,1894],{"class":64},"  \u002F\u002F ProductGrid re-renders using deferredQuery, which \"lags behind\" query\n",[33,1896,1897],{"class":35,"line":201},[33,1898,1899],{"class":64},"  \u002F\u002F during rapid typing — React prioritizes keeping the input itself\n",[33,1901,1902],{"class":35,"line":210},[33,1903,1904],{"class":64},"  \u002F\u002F (bound to query, not deferredQuery) responsive.\n",[33,1906,1907,1909,1912,1914,1917],{"class":35,"line":222},[33,1908,107],{"class":39},[33,1910,1911],{"class":113}," filtered",[33,1913,601],{"class":39},[33,1915,1916],{"class":97}," useMemo",[33,1918,1919],{"class":43},"(\n",[33,1921,1922,1925,1927,1930,1932,1934,1936],{"class":35,"line":234},[33,1923,1924],{"class":43},"    () ",[33,1926,401],{"class":39},[33,1928,1929],{"class":43}," allProducts.",[33,1931,647],{"class":97},[33,1933,132],{"class":43},[33,1935,652],{"class":386},[33,1937,655],{"class":39},[33,1939,1940,1943,1945,1947,1949,1952,1954],{"class":35,"line":240},[33,1941,1942],{"class":43},"      p.name.",[33,1944,663],{"class":97},[33,1946,666],{"class":43},[33,1948,669],{"class":97},[33,1950,1951],{"class":43},"(deferredQuery.",[33,1953,663],{"class":97},[33,1955,677],{"class":43},[33,1957,1958],{"class":35,"line":246},[33,1959,1960],{"class":43},"    ),\n",[33,1962,1963],{"class":35,"line":255},[33,1964,1965],{"class":43},"    [allProducts, deferredQuery]\n",[33,1967,1968],{"class":35,"line":267},[33,1969,770],{"class":43},[33,1971,1972],{"class":35,"line":273},[33,1973,58],{"emptyLinePlaceholder":57},[33,1975,1976,1978,1981,1983,1986,1989,1992],{"class":35,"line":279},[33,1977,107],{"class":39},[33,1979,1980],{"class":113}," isStale",[33,1982,601],{"class":39},[33,1984,1985],{"class":43}," query ",[33,1987,1988],{"class":39},"!==",[33,1990,1991],{"class":43}," deferredQuery  ",[33,1993,1994],{"class":64},"\u002F\u002F true while a deferred update is in flight\n",[33,1996,1997],{"class":35,"line":284},[33,1998,58],{"emptyLinePlaceholder":57},[33,2000,2001,2003],{"class":35,"line":318},[33,2002,287],{"class":39},[33,2004,703],{"class":43},[33,2006,2007,2009,2011],{"class":35,"line":698},[33,2008,709],{"class":43},[33,2010,712],{"class":293},[33,2012,315],{"class":43},[33,2014,2015,2017,2019,2021,2023,2025,2027,2029,2032,2034,2036,2039],{"class":35,"line":706},[33,2016,720],{"class":43},[33,2018,723],{"class":293},[33,2020,598],{"class":97},[33,2022,126],{"class":39},[33,2024,730],{"class":43},[33,2026,733],{"class":97},[33,2028,126],{"class":39},[33,2030,2031],{"class":43},"{",[33,2033,588],{"class":386},[33,2035,1021],{"class":39},[33,2037,2038],{"class":97}," setQuery",[33,2040,2041],{"class":43},"(e.target.value)} \u002F>\n",[33,2043,2044,2046,2048,2050,2052,2055,2057,2059,2061,2063],{"class":35,"line":717},[33,2045,720],{"class":43},[33,2047,712],{"class":293},[33,2049,1313],{"class":97},[33,2051,126],{"class":39},[33,2053,2054],{"class":43},"{{ opacity: isStale ",[33,2056,1321],{"class":39},[33,2058,1324],{"class":113},[33,2060,1327],{"class":39},[33,2062,1330],{"class":113},[33,2064,1333],{"class":43},[33,2066,2067,2069,2071,2073,2075],{"class":35,"line":741},[33,2068,1338],{"class":43},[33,2070,746],{"class":113},[33,2072,749],{"class":97},[33,2074,126],{"class":39},[33,2076,754],{"class":43},[33,2078,2079,2081,2083],{"class":35,"line":757},[33,2080,1351],{"class":43},[33,2082,712],{"class":293},[33,2084,315],{"class":43},[33,2086,2087,2089,2091],{"class":35,"line":767},[33,2088,760],{"class":43},[33,2090,712],{"class":293},[33,2092,315],{"class":43},[33,2094,2095],{"class":35,"line":773},[33,2096,770],{"class":43},[33,2098,2099],{"class":35,"line":778},[33,2100,321],{"class":43},[33,2102,2103],{"class":35,"line":783},[33,2104,58],{"emptyLinePlaceholder":57},[33,2106,2107],{"class":35,"line":789},[33,2108,2109],{"class":64},"\u002F\u002F PRACTICAL DIFFERENCE from startTransition:\n",[33,2111,2112],{"class":35,"line":795},[33,2113,2114],{"class":64},"\u002F\u002F - startTransition: you OWN the setState call -> wrap it in startTransition\n",[33,2116,2117],{"class":35,"line":801},[33,2118,2119],{"class":64},"\u002F\u002F - useDeferredValue: you RECEIVE a value (prop, library hook) -> defer it\n",[33,2121,2123],{"class":35,"line":2122},38,[33,2124,2125],{"class":64},"\u002F\u002F You can't wrap someone else's state setter in startTransition, but you\n",[33,2127,2129],{"class":35,"line":2128},39,[33,2130,2131],{"class":64},"\u002F\u002F CAN defer any value you have access to.\n",[323,2133,2135],{"id":2134},"usedeferredvalue-for-filtered-lists","useDeferredValue for Filtered Lists",[19,2137,2139],{"filename":2138,"language":22},"deferred_list.js",[24,2140,2142],{"className":26,"code":2141,"language":22,"meta":28,"style":28},"import { useDeferredValue, useMemo, memo } from 'react'\n\n\u002F\u002F The memoized child receives the deferred value — it only re-renders\n\u002F\u002F when deferredQuery changes (lagging behind the urgent query updates).\n\nconst SearchResults = memo(function SearchResults({ query, items }) {\n  \u002F\u002F This component renders only when `query` (the deferred value) changes.\n  \u002F\u002F During rapid typing, it skips intermediate renders — React processes\n  \u002F\u002F the urgent input updates first, then catches up on the deferred value.\n  const results = useMemo(() => {\n    const lower = query.toLowerCase()\n    return items.filter(i => i.name.toLowerCase().includes(lower))\n  }, [query, items])\n\n  return (\n    \u003Cul>\n      {results.map(r => \u003Cli key={r.id}>{r.name}\u003C\u002Fli>)}\n    \u003C\u002Ful>\n  )\n})\n\nfunction SearchPage({ items }) {\n  const [query, setQuery] = useState('')\n  const deferredQuery = useDeferredValue(query)\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n      \u003CSearchResults query={deferredQuery} items={items} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,2143,2144,2155,2159,2164,2169,2173,2202,2207,2212,2217,2233,2249,2278,2283,2287,2293,2302,2335,2343,2347,2351,2355,2368,2392,2404,2408,2414,2422,2448,2470,2478,2482],{"__ignoreMap":28},[33,2145,2146,2148,2151,2153],{"class":35,"line":36},[33,2147,40],{"class":39},[33,2149,2150],{"class":43}," { useDeferredValue, useMemo, memo } ",[33,2152,47],{"class":39},[33,2154,51],{"class":50},[33,2156,2157],{"class":35,"line":54},[33,2158,58],{"emptyLinePlaceholder":57},[33,2160,2161],{"class":35,"line":61},[33,2162,2163],{"class":64},"\u002F\u002F The memoized child receives the deferred value — it only re-renders\n",[33,2165,2166],{"class":35,"line":68},[33,2167,2168],{"class":64},"\u002F\u002F when deferredQuery changes (lagging behind the urgent query updates).\n",[33,2170,2171],{"class":35,"line":74},[33,2172,58],{"emptyLinePlaceholder":57},[33,2174,2175,2177,2180,2182,2185,2187,2189,2191,2193,2195,2197,2200],{"class":35,"line":80},[33,2176,1441],{"class":39},[33,2178,2179],{"class":113}," SearchResults",[33,2181,601],{"class":39},[33,2183,2184],{"class":97}," memo",[33,2186,132],{"class":43},[33,2188,94],{"class":39},[33,2190,2179],{"class":97},[33,2192,515],{"class":43},[33,2194,530],{"class":386},[33,2196,117],{"class":43},[33,2198,2199],{"class":386},"items",[33,2201,521],{"class":43},[33,2203,2204],{"class":35,"line":86},[33,2205,2206],{"class":64},"  \u002F\u002F This component renders only when `query` (the deferred value) changes.\n",[33,2208,2209],{"class":35,"line":91},[33,2210,2211],{"class":64},"  \u002F\u002F During rapid typing, it skips intermediate renders — React processes\n",[33,2213,2214],{"class":35,"line":104},[33,2215,2216],{"class":64},"  \u002F\u002F the urgent input updates first, then catches up on the deferred value.\n",[33,2218,2219,2221,2223,2225,2227,2229,2231],{"class":35,"line":141},[33,2220,107],{"class":39},[33,2222,905],{"class":113},[33,2224,601],{"class":39},[33,2226,1916],{"class":97},[33,2228,398],{"class":43},[33,2230,401],{"class":39},[33,2232,207],{"class":43},[33,2234,2235,2237,2240,2242,2245,2247],{"class":35,"line":169},[33,2236,595],{"class":39},[33,2238,2239],{"class":113}," lower",[33,2241,601],{"class":39},[33,2243,2244],{"class":43}," query.",[33,2246,663],{"class":97},[33,2248,219],{"class":43},[33,2250,2251,2254,2257,2259,2261,2264,2266,2269,2271,2273,2275],{"class":35,"line":174},[33,2252,2253],{"class":39},"    return",[33,2255,2256],{"class":43}," items.",[33,2258,647],{"class":97},[33,2260,132],{"class":43},[33,2262,2263],{"class":386},"i",[33,2265,1021],{"class":39},[33,2267,2268],{"class":43}," i.name.",[33,2270,663],{"class":97},[33,2272,666],{"class":43},[33,2274,669],{"class":97},[33,2276,2277],{"class":43},"(lower))\n",[33,2279,2280],{"class":35,"line":188},[33,2281,2282],{"class":43},"  }, [query, items])\n",[33,2284,2285],{"class":35,"line":201},[33,2286,58],{"emptyLinePlaceholder":57},[33,2288,2289,2291],{"class":35,"line":210},[33,2290,287],{"class":39},[33,2292,703],{"class":43},[33,2294,2295,2297,2300],{"class":35,"line":222},[33,2296,709],{"class":43},[33,2298,2299],{"class":293},"ul",[33,2301,315],{"class":43},[33,2303,2304,2307,2310,2312,2315,2317,2319,2322,2325,2327,2330,2332],{"class":35,"line":234},[33,2305,2306],{"class":43},"      {results.",[33,2308,2309],{"class":97},"map",[33,2311,132],{"class":43},[33,2313,2314],{"class":386},"r",[33,2316,1021],{"class":39},[33,2318,290],{"class":43},[33,2320,2321],{"class":293},"li",[33,2323,2324],{"class":97}," key",[33,2326,126],{"class":39},[33,2328,2329],{"class":43},"{r.id}>{r.name}\u003C\u002F",[33,2331,2321],{"class":293},[33,2333,2334],{"class":43},">)}\n",[33,2336,2337,2339,2341],{"class":35,"line":240},[33,2338,760],{"class":43},[33,2340,2299],{"class":293},[33,2342,315],{"class":43},[33,2344,2345],{"class":35,"line":246},[33,2346,770],{"class":43},[33,2348,2349],{"class":35,"line":255},[33,2350,853],{"class":43},[33,2352,2353],{"class":35,"line":267},[33,2354,58],{"emptyLinePlaceholder":57},[33,2356,2357,2359,2362,2364,2366],{"class":35,"line":273},[33,2358,94],{"class":39},[33,2360,2361],{"class":97}," SearchPage",[33,2363,515],{"class":43},[33,2365,2199],{"class":386},[33,2367,521],{"class":43},[33,2369,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388,2390],{"class":35,"line":279},[33,2371,107],{"class":39},[33,2373,110],{"class":43},[33,2375,530],{"class":113},[33,2377,117],{"class":43},[33,2379,535],{"class":113},[33,2381,123],{"class":43},[33,2383,126],{"class":39},[33,2385,129],{"class":97},[33,2387,132],{"class":43},[33,2389,546],{"class":50},[33,2391,138],{"class":43},[33,2393,2394,2396,2398,2400,2402],{"class":35,"line":284},[33,2395,107],{"class":39},[33,2397,1877],{"class":113},[33,2399,601],{"class":39},[33,2401,1882],{"class":97},[33,2403,1885],{"class":43},[33,2405,2406],{"class":35,"line":318},[33,2407,58],{"emptyLinePlaceholder":57},[33,2409,2410,2412],{"class":35,"line":698},[33,2411,287],{"class":39},[33,2413,703],{"class":43},[33,2415,2416,2418,2420],{"class":35,"line":706},[33,2417,709],{"class":43},[33,2419,712],{"class":293},[33,2421,315],{"class":43},[33,2423,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446],{"class":35,"line":717},[33,2425,720],{"class":43},[33,2427,723],{"class":293},[33,2429,598],{"class":97},[33,2431,126],{"class":39},[33,2433,730],{"class":43},[33,2435,733],{"class":97},[33,2437,126],{"class":39},[33,2439,2031],{"class":43},[33,2441,588],{"class":386},[33,2443,1021],{"class":39},[33,2445,2038],{"class":97},[33,2447,2041],{"class":43},[33,2449,2450,2452,2455,2458,2460,2463,2465,2467],{"class":35,"line":741},[33,2451,720],{"class":43},[33,2453,2454],{"class":113},"SearchResults",[33,2456,2457],{"class":97}," query",[33,2459,126],{"class":39},[33,2461,2462],{"class":43},"{deferredQuery} ",[33,2464,2199],{"class":97},[33,2466,126],{"class":39},[33,2468,2469],{"class":43},"{items} \u002F>\n",[33,2471,2472,2474,2476],{"class":35,"line":757},[33,2473,760],{"class":43},[33,2475,712],{"class":293},[33,2477,315],{"class":43},[33,2479,2480],{"class":35,"line":767},[33,2481,770],{"class":43},[33,2483,2484],{"class":35,"line":773},[33,2485,321],{"class":43},[14,2487,2489],{"id":2488},"suspense-for-data-fetching","Suspense for Data Fetching",[19,2491,2493],{"filename":2492,"language":22},"suspense_data.js",[24,2494,2496],{"className":26,"code":2495,"language":22,"meta":28,"style":28},"import { Suspense } from 'react'\n\n\u002F\u002F Ch 20 covered \u003CSuspense> paired with React.lazy for code loading.\n\u002F\u002F The same primitive extends to DATA — a component can \"suspend\" (signal\n\u002F\u002F it isn't ready to render yet) while data loads, letting a \u003CSuspense>\n\u002F\u002F boundary show fallback UI — unifying loading states for code and data.\n\n\u002F\u002F Conceptual illustration — the exact suspense-enabled fetching API is\n\u002F\u002F provided by a data-fetching library (React Query's useSuspenseQuery)\n\u002F\u002F or framework (Next.js App Router), NOT hand-rolled fetch calls.\n\n\u002F\u002F With React Query's useSuspenseQuery:\nimport { useSuspenseQuery } from '@tanstack\u002Freact-query'\n\nfunction ProductDetail({ productId }) {\n  const { data: product } = useSuspenseQuery({\n    queryKey: ['product', productId],\n    queryFn: () => fetchProduct(productId),\n  })\n  \u002F\u002F No loading branch here at all — if data isn't ready, this component\n  \u002F\u002F \"suspends,\" and the nearest ancestor \u003CSuspense> shows its fallback\n  \u002F\u002F instead of this component's output.\n  return \u003Ch1>{product.name}\u003C\u002Fh1>\n}\n\nfunction App() {\n  return (\n    \u003CSuspense fallback={\u003CSpinner \u002F>}>\n      \u003CProductDetail productId=\"42\" \u002F>\n    \u003C\u002FSuspense>\n  )\n}\n",[30,2497,2498,2509,2513,2518,2523,2528,2533,2537,2542,2547,2552,2556,2561,2573,2577,2591,2618,2629,2645,2649,2654,2659,2664,2679,2683,2687,2696,2702,2718,2736,2744,2748],{"__ignoreMap":28},[33,2499,2500,2502,2505,2507],{"class":35,"line":36},[33,2501,40],{"class":39},[33,2503,2504],{"class":43}," { Suspense } ",[33,2506,47],{"class":39},[33,2508,51],{"class":50},[33,2510,2511],{"class":35,"line":54},[33,2512,58],{"emptyLinePlaceholder":57},[33,2514,2515],{"class":35,"line":61},[33,2516,2517],{"class":64},"\u002F\u002F Ch 20 covered \u003CSuspense> paired with React.lazy for code loading.\n",[33,2519,2520],{"class":35,"line":68},[33,2521,2522],{"class":64},"\u002F\u002F The same primitive extends to DATA — a component can \"suspend\" (signal\n",[33,2524,2525],{"class":35,"line":74},[33,2526,2527],{"class":64},"\u002F\u002F it isn't ready to render yet) while data loads, letting a \u003CSuspense>\n",[33,2529,2530],{"class":35,"line":80},[33,2531,2532],{"class":64},"\u002F\u002F boundary show fallback UI — unifying loading states for code and data.\n",[33,2534,2535],{"class":35,"line":86},[33,2536,58],{"emptyLinePlaceholder":57},[33,2538,2539],{"class":35,"line":91},[33,2540,2541],{"class":64},"\u002F\u002F Conceptual illustration — the exact suspense-enabled fetching API is\n",[33,2543,2544],{"class":35,"line":104},[33,2545,2546],{"class":64},"\u002F\u002F provided by a data-fetching library (React Query's useSuspenseQuery)\n",[33,2548,2549],{"class":35,"line":141},[33,2550,2551],{"class":64},"\u002F\u002F or framework (Next.js App Router), NOT hand-rolled fetch calls.\n",[33,2553,2554],{"class":35,"line":169},[33,2555,58],{"emptyLinePlaceholder":57},[33,2557,2558],{"class":35,"line":174},[33,2559,2560],{"class":64},"\u002F\u002F With React Query's useSuspenseQuery:\n",[33,2562,2563,2565,2568,2570],{"class":35,"line":188},[33,2564,40],{"class":39},[33,2566,2567],{"class":43}," { useSuspenseQuery } ",[33,2569,47],{"class":39},[33,2571,2572],{"class":50}," '@tanstack\u002Freact-query'\n",[33,2574,2575],{"class":35,"line":201},[33,2576,58],{"emptyLinePlaceholder":57},[33,2578,2579,2581,2584,2586,2589],{"class":35,"line":210},[33,2580,94],{"class":39},[33,2582,2583],{"class":97}," ProductDetail",[33,2585,515],{"class":43},[33,2587,2588],{"class":386},"productId",[33,2590,521],{"class":43},[33,2592,2593,2595,2598,2601,2604,2607,2610,2612,2615],{"class":35,"line":222},[33,2594,107],{"class":39},[33,2596,2597],{"class":43}," { ",[33,2599,2600],{"class":386},"data",[33,2602,2603],{"class":43},": ",[33,2605,2606],{"class":113},"product",[33,2608,2609],{"class":43}," } ",[33,2611,126],{"class":39},[33,2613,2614],{"class":97}," useSuspenseQuery",[33,2616,2617],{"class":43},"({\n",[33,2619,2620,2623,2626],{"class":35,"line":234},[33,2621,2622],{"class":43},"    queryKey: [",[33,2624,2625],{"class":50},"'product'",[33,2627,2628],{"class":43},", productId],\n",[33,2630,2631,2634,2637,2639,2642],{"class":35,"line":240},[33,2632,2633],{"class":97},"    queryFn",[33,2635,2636],{"class":43},": () ",[33,2638,401],{"class":39},[33,2640,2641],{"class":97}," fetchProduct",[33,2643,2644],{"class":43},"(productId),\n",[33,2646,2647],{"class":35,"line":246},[33,2648,419],{"class":43},[33,2650,2651],{"class":35,"line":255},[33,2652,2653],{"class":64},"  \u002F\u002F No loading branch here at all — if data isn't ready, this component\n",[33,2655,2656],{"class":35,"line":267},[33,2657,2658],{"class":64},"  \u002F\u002F \"suspends,\" and the nearest ancestor \u003CSuspense> shows its fallback\n",[33,2660,2661],{"class":35,"line":273},[33,2662,2663],{"class":64},"  \u002F\u002F instead of this component's output.\n",[33,2665,2666,2668,2670,2672,2675,2677],{"class":35,"line":279},[33,2667,287],{"class":39},[33,2669,290],{"class":43},[33,2671,10],{"class":293},[33,2673,2674],{"class":43},">{product.name}\u003C\u002F",[33,2676,10],{"class":293},[33,2678,315],{"class":43},[33,2680,2681],{"class":35,"line":284},[33,2682,321],{"class":43},[33,2684,2685],{"class":35,"line":318},[33,2686,58],{"emptyLinePlaceholder":57},[33,2688,2689,2691,2694],{"class":35,"line":698},[33,2690,94],{"class":39},[33,2692,2693],{"class":97}," App",[33,2695,101],{"class":43},[33,2697,2698,2700],{"class":35,"line":706},[33,2699,287],{"class":39},[33,2701,703],{"class":43},[33,2703,2704,2706,2708,2710,2712,2714,2716],{"class":35,"line":717},[33,2705,709],{"class":43},[33,2707,1707],{"class":113},[33,2709,1710],{"class":97},[33,2711,126],{"class":39},[33,2713,1715],{"class":43},[33,2715,1718],{"class":113},[33,2717,1721],{"class":43},[33,2719,2720,2722,2725,2728,2730,2733],{"class":35,"line":741},[33,2721,720],{"class":43},[33,2723,2724],{"class":113},"ProductDetail",[33,2726,2727],{"class":97}," productId",[33,2729,126],{"class":39},[33,2731,2732],{"class":50},"\"42\"",[33,2734,2735],{"class":43}," \u002F>\n",[33,2737,2738,2740,2742],{"class":35,"line":757},[33,2739,760],{"class":43},[33,2741,1707],{"class":113},[33,2743,315],{"class":43},[33,2745,2746],{"class":35,"line":767},[33,2747,770],{"class":43},[33,2749,2750],{"class":35,"line":773},[33,2751,321],{"class":43},[323,2753,2755],{"id":2754},"suspense-with-nested-data-loading","Suspense with Nested Data Loading",[19,2757,2759],{"filename":2758,"language":22},"suspense_nested.js",[24,2760,2762],{"className":26,"code":2761,"language":22,"meta":28,"style":28},"import { Suspense } from 'react'\n\n\u002F\u002F Nested Suspense boundaries let different parts of the page load\n\u002F\u002F independently — the outer boundary shows a page-level fallback,\n\u002F\u002F inner boundaries show section-level fallbacks as each resolves.\n\nfunction ProductPage({ productId }) {\n  return (\n    \u003CSuspense fallback={\u003CPageSkeleton \u002F>}>\n      \u003CProductInfo productId={productId} \u002F>\n      \u003CSuspense fallback={\u003CReviewsSkeleton \u002F>}>\n        \u003CProductReviews productId={productId} \u002F>\n      \u003C\u002FSuspense>\n      \u003CSuspense fallback={\u003CRelatedSkeleton \u002F>}>\n        \u003CRelatedProducts productId={productId} \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002FSuspense>\n  )\n}\n\n\u002F\u002F ProductInfo, ProductReviews, and RelatedProducts each suspend\n\u002F\u002F independently — the page shell + product info appear first, then\n\u002F\u002F reviews and related products stream in as their data resolves.\n",[30,2763,2764,2774,2778,2783,2788,2793,2797,2810,2816,2833,2847,2864,2877,2885,2902,2915,2923,2931,2935,2939,2943,2948,2953],{"__ignoreMap":28},[33,2765,2766,2768,2770,2772],{"class":35,"line":36},[33,2767,40],{"class":39},[33,2769,2504],{"class":43},[33,2771,47],{"class":39},[33,2773,51],{"class":50},[33,2775,2776],{"class":35,"line":54},[33,2777,58],{"emptyLinePlaceholder":57},[33,2779,2780],{"class":35,"line":61},[33,2781,2782],{"class":64},"\u002F\u002F Nested Suspense boundaries let different parts of the page load\n",[33,2784,2785],{"class":35,"line":68},[33,2786,2787],{"class":64},"\u002F\u002F independently — the outer boundary shows a page-level fallback,\n",[33,2789,2790],{"class":35,"line":74},[33,2791,2792],{"class":64},"\u002F\u002F inner boundaries show section-level fallbacks as each resolves.\n",[33,2794,2795],{"class":35,"line":80},[33,2796,58],{"emptyLinePlaceholder":57},[33,2798,2799,2801,2804,2806,2808],{"class":35,"line":86},[33,2800,94],{"class":39},[33,2802,2803],{"class":97}," ProductPage",[33,2805,515],{"class":43},[33,2807,2588],{"class":386},[33,2809,521],{"class":43},[33,2811,2812,2814],{"class":35,"line":91},[33,2813,287],{"class":39},[33,2815,703],{"class":43},[33,2817,2818,2820,2822,2824,2826,2828,2831],{"class":35,"line":104},[33,2819,709],{"class":43},[33,2821,1707],{"class":113},[33,2823,1710],{"class":97},[33,2825,126],{"class":39},[33,2827,1715],{"class":43},[33,2829,2830],{"class":113},"PageSkeleton",[33,2832,1721],{"class":43},[33,2834,2835,2837,2840,2842,2844],{"class":35,"line":141},[33,2836,720],{"class":43},[33,2838,2839],{"class":113},"ProductInfo",[33,2841,2727],{"class":97},[33,2843,126],{"class":39},[33,2845,2846],{"class":43},"{productId} \u002F>\n",[33,2848,2849,2851,2853,2855,2857,2859,2862],{"class":35,"line":169},[33,2850,720],{"class":43},[33,2852,1707],{"class":113},[33,2854,1710],{"class":97},[33,2856,126],{"class":39},[33,2858,1715],{"class":43},[33,2860,2861],{"class":113},"ReviewsSkeleton",[33,2863,1721],{"class":43},[33,2865,2866,2868,2871,2873,2875],{"class":35,"line":174},[33,2867,1338],{"class":43},[33,2869,2870],{"class":113},"ProductReviews",[33,2872,2727],{"class":97},[33,2874,126],{"class":39},[33,2876,2846],{"class":43},[33,2878,2879,2881,2883],{"class":35,"line":188},[33,2880,1351],{"class":43},[33,2882,1707],{"class":113},[33,2884,315],{"class":43},[33,2886,2887,2889,2891,2893,2895,2897,2900],{"class":35,"line":201},[33,2888,720],{"class":43},[33,2890,1707],{"class":113},[33,2892,1710],{"class":97},[33,2894,126],{"class":39},[33,2896,1715],{"class":43},[33,2898,2899],{"class":113},"RelatedSkeleton",[33,2901,1721],{"class":43},[33,2903,2904,2906,2909,2911,2913],{"class":35,"line":210},[33,2905,1338],{"class":43},[33,2907,2908],{"class":113},"RelatedProducts",[33,2910,2727],{"class":97},[33,2912,126],{"class":39},[33,2914,2846],{"class":43},[33,2916,2917,2919,2921],{"class":35,"line":222},[33,2918,1351],{"class":43},[33,2920,1707],{"class":113},[33,2922,315],{"class":43},[33,2924,2925,2927,2929],{"class":35,"line":234},[33,2926,760],{"class":43},[33,2928,1707],{"class":113},[33,2930,315],{"class":43},[33,2932,2933],{"class":35,"line":240},[33,2934,770],{"class":43},[33,2936,2937],{"class":35,"line":246},[33,2938,321],{"class":43},[33,2940,2941],{"class":35,"line":255},[33,2942,58],{"emptyLinePlaceholder":57},[33,2944,2945],{"class":35,"line":267},[33,2946,2947],{"class":64},"\u002F\u002F ProductInfo, ProductReviews, and RelatedProducts each suspend\n",[33,2949,2950],{"class":35,"line":273},[33,2951,2952],{"class":64},"\u002F\u002F independently — the page shell + product info appear first, then\n",[33,2954,2955],{"class":35,"line":279},[33,2956,2957],{"class":64},"\u002F\u002F reviews and related products stream in as their data resolves.\n",[323,2959,2961],{"id":2960},"why-hand-rolled-suspense-doesnt-work","Why Hand-Rolled Suspense Doesn't Work",[19,2963,2965],{"filename":2964,"language":22},"suspense_pitfall.js",[24,2966,2968],{"className":26,"code":2967,"language":22,"meta":28,"style":28},"\u002F\u002F BROKEN: throwing a raw, uncached promise from a component's render body\n\u002F\u002F produces an INFINITE RE-RENDER LOOP, not a working suspense boundary.\n\u002F\u002F A new promise is thrown on every render attempt with nothing to recognize\n\u002F\u002F it as \"the same\" pending request.\n\nfunction BrokenProductDetail({ productId }) {\n  \u002F\u002F Every render creates a NEW promise and throws it — React catches it,\n  \u002F\u002F shows fallback, then tries to re-render, creates ANOTHER new promise...\n  throw fetch(`\u002Fapi\u002Fproducts\u002F${productId}`).then(r => r.json())\n}\n\n\u002F\u002F A correct Suspense integration requires:\n\u002F\u002F 1. Caching: the same promise must be returned for the same key until it\n\u002F\u002F    resolves, so re-render attempts get the SAME promise, not a new one.\n\u002F\u002F 2. Throwing the promise from render (not useEffect) — React catches it.\n\u002F\u002F 3. After resolution, the cache returns the resolved value, not a promise.\n\n\u002F\u002F This is why production Suspense-for-data uses libraries (React Query,\n\u002F\u002F Relay, Next.js) that handle this contract correctly — don't reinvent it.\n",[30,2969,2970,2975,2980,2985,2990,2994,3007,3012,3017,3054,3058,3062,3067,3072,3077,3082,3087,3091,3096],{"__ignoreMap":28},[33,2971,2972],{"class":35,"line":36},[33,2973,2974],{"class":64},"\u002F\u002F BROKEN: throwing a raw, uncached promise from a component's render body\n",[33,2976,2977],{"class":35,"line":54},[33,2978,2979],{"class":64},"\u002F\u002F produces an INFINITE RE-RENDER LOOP, not a working suspense boundary.\n",[33,2981,2982],{"class":35,"line":61},[33,2983,2984],{"class":64},"\u002F\u002F A new promise is thrown on every render attempt with nothing to recognize\n",[33,2986,2987],{"class":35,"line":68},[33,2988,2989],{"class":64},"\u002F\u002F it as \"the same\" pending request.\n",[33,2991,2992],{"class":35,"line":74},[33,2993,58],{"emptyLinePlaceholder":57},[33,2995,2996,2998,3001,3003,3005],{"class":35,"line":80},[33,2997,94],{"class":39},[33,2999,3000],{"class":97}," BrokenProductDetail",[33,3002,515],{"class":43},[33,3004,2588],{"class":386},[33,3006,521],{"class":43},[33,3008,3009],{"class":35,"line":86},[33,3010,3011],{"class":64},"  \u002F\u002F Every render creates a NEW promise and throws it — React catches it,\n",[33,3013,3014],{"class":35,"line":91},[33,3015,3016],{"class":64},"  \u002F\u002F shows fallback, then tries to re-render, creates ANOTHER new promise...\n",[33,3018,3019,3022,3025,3027,3030,3032,3035,3038,3040,3042,3044,3046,3049,3052],{"class":35,"line":104},[33,3020,3021],{"class":39},"  throw",[33,3023,3024],{"class":97}," fetch",[33,3026,132],{"class":43},[33,3028,3029],{"class":50},"`\u002Fapi\u002Fproducts\u002F${",[33,3031,2588],{"class":43},[33,3033,3034],{"class":50},"}`",[33,3036,3037],{"class":43},").",[33,3039,1013],{"class":97},[33,3041,132],{"class":43},[33,3043,2314],{"class":386},[33,3045,1021],{"class":39},[33,3047,3048],{"class":43}," r.",[33,3050,3051],{"class":97},"json",[33,3053,677],{"class":43},[33,3055,3056],{"class":35,"line":141},[33,3057,321],{"class":43},[33,3059,3060],{"class":35,"line":169},[33,3061,58],{"emptyLinePlaceholder":57},[33,3063,3064],{"class":35,"line":174},[33,3065,3066],{"class":64},"\u002F\u002F A correct Suspense integration requires:\n",[33,3068,3069],{"class":35,"line":188},[33,3070,3071],{"class":64},"\u002F\u002F 1. Caching: the same promise must be returned for the same key until it\n",[33,3073,3074],{"class":35,"line":201},[33,3075,3076],{"class":64},"\u002F\u002F    resolves, so re-render attempts get the SAME promise, not a new one.\n",[33,3078,3079],{"class":35,"line":210},[33,3080,3081],{"class":64},"\u002F\u002F 2. Throwing the promise from render (not useEffect) — React catches it.\n",[33,3083,3084],{"class":35,"line":222},[33,3085,3086],{"class":64},"\u002F\u002F 3. After resolution, the cache returns the resolved value, not a promise.\n",[33,3088,3089],{"class":35,"line":234},[33,3090,58],{"emptyLinePlaceholder":57},[33,3092,3093],{"class":35,"line":240},[33,3094,3095],{"class":64},"\u002F\u002F This is why production Suspense-for-data uses libraries (React Query,\n",[33,3097,3098],{"class":35,"line":246},[33,3099,3100],{"class":64},"\u002F\u002F Relay, Next.js) that handle this contract correctly — don't reinvent it.\n",[14,3102,3104],{"id":3103},"concurrent-rendering-requires-purity","Concurrent Rendering Requires Purity",[19,3106,3108],{"filename":3107,"language":22},"purity_requirement.js",[24,3109,3111],{"className":26,"code":3110,"language":22,"meta":28,"style":28},"\u002F\u002F Concurrent rendering means React may call a component's render function,\n\u002F\u002F pause partway through the tree, discard that render entirely without\n\u002F\u002F committing it, and start over. This is impossible pre-18 (a render, once\n\u002F\u002F started, always ran to completion and committed).\n\nlet renderCount = 0\n\nfunction ProductCard({ product }) {\n  renderCount++  \u002F\u002F IMPURE: a side effect during render\n  console.log(`Rendered ${renderCount} times`)\n  \u002F\u002F Under concurrent rendering, React may call this function, discard the\n  \u002F\u002F result without committing\u002Fpainting it, and call it again — renderCount\n  \u002F\u002F now over-counts, reporting numbers that don't correspond to actual\n  \u002F\u002F commits the user ever saw.\n  return \u003Cdiv>{product.name}\u003C\u002Fdiv>\n}\n\n\u002F\u002F CORRECT: side effects belong in useEffect (which only runs after commit),\n\u002F\u002F not in the render body. Render must be PURE — same input, same output,\n\u002F\u002F no external mutations.\nfunction PureProductCard({ product }) {\n  \u002F\u002F Computing derived values during render is fine (it's pure):\n  const displayName = product.name.toUpperCase()\n  return \u003Cdiv>{displayName}\u003C\u002Fdiv>\n}\n",[30,3112,3113,3118,3123,3128,3133,3137,3150,3154,3167,3178,3199,3204,3209,3214,3219,3233,3237,3241,3246,3251,3256,3269,3274,3291,3306],{"__ignoreMap":28},[33,3114,3115],{"class":35,"line":36},[33,3116,3117],{"class":64},"\u002F\u002F Concurrent rendering means React may call a component's render function,\n",[33,3119,3120],{"class":35,"line":54},[33,3121,3122],{"class":64},"\u002F\u002F pause partway through the tree, discard that render entirely without\n",[33,3124,3125],{"class":35,"line":61},[33,3126,3127],{"class":64},"\u002F\u002F committing it, and start over. This is impossible pre-18 (a render, once\n",[33,3129,3130],{"class":35,"line":68},[33,3131,3132],{"class":64},"\u002F\u002F started, always ran to completion and committed).\n",[33,3134,3135],{"class":35,"line":74},[33,3136,58],{"emptyLinePlaceholder":57},[33,3138,3139,3142,3145,3147],{"class":35,"line":80},[33,3140,3141],{"class":39},"let",[33,3143,3144],{"class":43}," renderCount ",[33,3146,126],{"class":39},[33,3148,3149],{"class":113}," 0\n",[33,3151,3152],{"class":35,"line":86},[33,3153,58],{"emptyLinePlaceholder":57},[33,3155,3156,3158,3161,3163,3165],{"class":35,"line":91},[33,3157,94],{"class":39},[33,3159,3160],{"class":97}," ProductCard",[33,3162,515],{"class":43},[33,3164,2606],{"class":386},[33,3166,521],{"class":43},[33,3168,3169,3172,3175],{"class":35,"line":104},[33,3170,3171],{"class":43},"  renderCount",[33,3173,3174],{"class":39},"++",[33,3176,3177],{"class":64},"  \u002F\u002F IMPURE: a side effect during render\n",[33,3179,3180,3183,3186,3188,3191,3194,3197],{"class":35,"line":141},[33,3181,3182],{"class":43},"  console.",[33,3184,3185],{"class":97},"log",[33,3187,132],{"class":43},[33,3189,3190],{"class":50},"`Rendered ${",[33,3192,3193],{"class":43},"renderCount",[33,3195,3196],{"class":50},"} times`",[33,3198,138],{"class":43},[33,3200,3201],{"class":35,"line":169},[33,3202,3203],{"class":64},"  \u002F\u002F Under concurrent rendering, React may call this function, discard the\n",[33,3205,3206],{"class":35,"line":174},[33,3207,3208],{"class":64},"  \u002F\u002F result without committing\u002Fpainting it, and call it again — renderCount\n",[33,3210,3211],{"class":35,"line":188},[33,3212,3213],{"class":64},"  \u002F\u002F now over-counts, reporting numbers that don't correspond to actual\n",[33,3215,3216],{"class":35,"line":201},[33,3217,3218],{"class":64},"  \u002F\u002F commits the user ever saw.\n",[33,3220,3221,3223,3225,3227,3229,3231],{"class":35,"line":210},[33,3222,287],{"class":39},[33,3224,290],{"class":43},[33,3226,712],{"class":293},[33,3228,2674],{"class":43},[33,3230,712],{"class":293},[33,3232,315],{"class":43},[33,3234,3235],{"class":35,"line":222},[33,3236,321],{"class":43},[33,3238,3239],{"class":35,"line":234},[33,3240,58],{"emptyLinePlaceholder":57},[33,3242,3243],{"class":35,"line":240},[33,3244,3245],{"class":64},"\u002F\u002F CORRECT: side effects belong in useEffect (which only runs after commit),\n",[33,3247,3248],{"class":35,"line":246},[33,3249,3250],{"class":64},"\u002F\u002F not in the render body. Render must be PURE — same input, same output,\n",[33,3252,3253],{"class":35,"line":255},[33,3254,3255],{"class":64},"\u002F\u002F no external mutations.\n",[33,3257,3258,3260,3263,3265,3267],{"class":35,"line":267},[33,3259,94],{"class":39},[33,3261,3262],{"class":97}," PureProductCard",[33,3264,515],{"class":43},[33,3266,2606],{"class":386},[33,3268,521],{"class":43},[33,3270,3271],{"class":35,"line":273},[33,3272,3273],{"class":64},"  \u002F\u002F Computing derived values during render is fine (it's pure):\n",[33,3275,3276,3278,3281,3283,3286,3289],{"class":35,"line":279},[33,3277,107],{"class":39},[33,3279,3280],{"class":113}," displayName",[33,3282,601],{"class":39},[33,3284,3285],{"class":43}," product.name.",[33,3287,3288],{"class":97},"toUpperCase",[33,3290,219],{"class":43},[33,3292,3293,3295,3297,3299,3302,3304],{"class":35,"line":284},[33,3294,287],{"class":39},[33,3296,290],{"class":43},[33,3298,712],{"class":293},[33,3300,3301],{"class":43},">{displayName}\u003C\u002F",[33,3303,712],{"class":293},[33,3305,315],{"class":43},[33,3307,3308],{"class":35,"line":318},[33,3309,321],{"class":43},[323,3311,3313],{"id":3312},"why-purity-matters-under-concurrent-rendering","Why Purity Matters Under Concurrent Rendering",[19,3315,3317],{"filename":3316,"language":22},"purity_concurrent.js",[24,3318,3320],{"className":26,"code":3319,"language":22,"meta":28,"style":28},"\u002F\u002F Under concurrent rendering, React can:\n\u002F\u002F 1. Start rendering a tree\n\u002F\u002F 2. Be interrupted by a higher-priority update (e.g., user input)\n\u002F\u002F 3. DISCARD the in-progress render entirely\n\u002F\u002F 4. Start a fresh render with the new state\n\n\u002F\u002F This means a component render function may be called MORE times than\n\u002F\u002F the component actually commits to the DOM. Any code that relies on\n\u002F\u002F \"render runs exactly once per commit\" breaks:\n\n\u002F\u002F BROKEN: mutating a ref during render to track \"first render\"\nfunction Component({ data }) {\n  if (!ref.current.initialized) {\n    ref.current.initialized = true\n    ref.current.processedData = expensiveProcess(data)  \u002F\u002F side effect in render\n  }\n  return \u003Cdiv>{ref.current.processedData}\u003C\u002Fdiv>\n}\n\u002F\u002F React may call this twice during a concurrent render, discard the first\n\u002F\u002F call, and the ref now holds state from a discarded render attempt.\n\n\u002F\u002F CORRECT: use useMemo for expensive derived state, useEffect for side effects\nfunction Component({ data }) {\n  const processedData = useMemo(() => expensiveProcess(data), [data])\n  return \u003Cdiv>{processedData}\u003C\u002Fdiv>\n}\n",[30,3321,3322,3327,3332,3337,3342,3347,3351,3356,3361,3366,3370,3375,3388,3402,3412,3428,3432,3447,3451,3456,3461,3465,3470,3482,3502,3517],{"__ignoreMap":28},[33,3323,3324],{"class":35,"line":36},[33,3325,3326],{"class":64},"\u002F\u002F Under concurrent rendering, React can:\n",[33,3328,3329],{"class":35,"line":54},[33,3330,3331],{"class":64},"\u002F\u002F 1. Start rendering a tree\n",[33,3333,3334],{"class":35,"line":61},[33,3335,3336],{"class":64},"\u002F\u002F 2. Be interrupted by a higher-priority update (e.g., user input)\n",[33,3338,3339],{"class":35,"line":68},[33,3340,3341],{"class":64},"\u002F\u002F 3. DISCARD the in-progress render entirely\n",[33,3343,3344],{"class":35,"line":74},[33,3345,3346],{"class":64},"\u002F\u002F 4. Start a fresh render with the new state\n",[33,3348,3349],{"class":35,"line":80},[33,3350,58],{"emptyLinePlaceholder":57},[33,3352,3353],{"class":35,"line":86},[33,3354,3355],{"class":64},"\u002F\u002F This means a component render function may be called MORE times than\n",[33,3357,3358],{"class":35,"line":91},[33,3359,3360],{"class":64},"\u002F\u002F the component actually commits to the DOM. Any code that relies on\n",[33,3362,3363],{"class":35,"line":104},[33,3364,3365],{"class":64},"\u002F\u002F \"render runs exactly once per commit\" breaks:\n",[33,3367,3368],{"class":35,"line":141},[33,3369,58],{"emptyLinePlaceholder":57},[33,3371,3372],{"class":35,"line":169},[33,3373,3374],{"class":64},"\u002F\u002F BROKEN: mutating a ref during render to track \"first render\"\n",[33,3376,3377,3379,3382,3384,3386],{"class":35,"line":174},[33,3378,94],{"class":39},[33,3380,3381],{"class":97}," Component",[33,3383,515],{"class":43},[33,3385,2600],{"class":386},[33,3387,521],{"class":43},[33,3389,3390,3393,3396,3399],{"class":35,"line":188},[33,3391,3392],{"class":39},"  if",[33,3394,3395],{"class":43}," (",[33,3397,3398],{"class":39},"!",[33,3400,3401],{"class":43},"ref.current.initialized) {\n",[33,3403,3404,3407,3409],{"class":35,"line":201},[33,3405,3406],{"class":43},"    ref.current.initialized ",[33,3408,126],{"class":39},[33,3410,3411],{"class":113}," true\n",[33,3413,3414,3417,3419,3422,3425],{"class":35,"line":210},[33,3415,3416],{"class":43},"    ref.current.processedData ",[33,3418,126],{"class":39},[33,3420,3421],{"class":97}," expensiveProcess",[33,3423,3424],{"class":43},"(data)  ",[33,3426,3427],{"class":64},"\u002F\u002F side effect in render\n",[33,3429,3430],{"class":35,"line":222},[33,3431,276],{"class":43},[33,3433,3434,3436,3438,3440,3443,3445],{"class":35,"line":234},[33,3435,287],{"class":39},[33,3437,290],{"class":43},[33,3439,712],{"class":293},[33,3441,3442],{"class":43},">{ref.current.processedData}\u003C\u002F",[33,3444,712],{"class":293},[33,3446,315],{"class":43},[33,3448,3449],{"class":35,"line":240},[33,3450,321],{"class":43},[33,3452,3453],{"class":35,"line":246},[33,3454,3455],{"class":64},"\u002F\u002F React may call this twice during a concurrent render, discard the first\n",[33,3457,3458],{"class":35,"line":255},[33,3459,3460],{"class":64},"\u002F\u002F call, and the ref now holds state from a discarded render attempt.\n",[33,3462,3463],{"class":35,"line":267},[33,3464,58],{"emptyLinePlaceholder":57},[33,3466,3467],{"class":35,"line":273},[33,3468,3469],{"class":64},"\u002F\u002F CORRECT: use useMemo for expensive derived state, useEffect for side effects\n",[33,3471,3472,3474,3476,3478,3480],{"class":35,"line":279},[33,3473,94],{"class":39},[33,3475,3381],{"class":97},[33,3477,515],{"class":43},[33,3479,2600],{"class":386},[33,3481,521],{"class":43},[33,3483,3484,3486,3489,3491,3493,3495,3497,3499],{"class":35,"line":284},[33,3485,107],{"class":39},[33,3487,3488],{"class":113}," processedData",[33,3490,601],{"class":39},[33,3492,1916],{"class":97},[33,3494,398],{"class":43},[33,3496,401],{"class":39},[33,3498,3421],{"class":97},[33,3500,3501],{"class":43},"(data), [data])\n",[33,3503,3504,3506,3508,3510,3513,3515],{"class":35,"line":318},[33,3505,287],{"class":39},[33,3507,290],{"class":43},[33,3509,712],{"class":293},[33,3511,3512],{"class":43},">{processedData}\u003C\u002F",[33,3514,712],{"class":293},[33,3516,315],{"class":43},[33,3518,3519],{"class":35,"line":698},[33,3520,321],{"class":43},[14,3522,3524],{"id":3523},"when-concurrent-helps-vs-doesnt","When Concurrent Helps vs Doesn't",[19,3526,3528],{"filename":3527,"language":22},"when_concurrent_helps.js",[24,3529,3531],{"className":26,"code":3530,"language":22,"meta":28,"style":28},"\u002F\u002F CONCURRENT HELPS when:\n\u002F\u002F 1. An expensive render would block urgent updates (typing, clicks)\n\u002F\u002F    -> useTransition\u002FuseDeferredValue lets urgent updates interrupt\n\u002F\u002F 2. Multiple state updates happen in the same tick\n\u002F\u002F    -> automatic batching reduces re-renders (no code change needed)\n\u002F\u002F 3. Parts of a page load data independently\n\u002F\u002F    -> Suspense boundaries let fast sections appear before slow ones\n\u002F\u002F 4. Tab\u002FRoute switching to heavy components\n\u002F\u002F    -> useTransition keeps the current tab interactive while the new one renders\n\n\u002F\u002F CONCURRENT DOESN'T HELP when:\n\u002F\u002F 1. The render itself is cheap (\u003C 1ms) — there's nothing to interrupt\n\u002F\u002F 2. There's only one state update — nothing to batch or prioritize\n\u002F\u002F 3. The bottleneck is network, not rendering — Suspense shows a spinner\n\u002F\u002F    either way; concurrent doesn't make the data arrive faster\n\u002F\u002F 4. The component is impure — concurrent makes it WORSE, not better\n\n\u002F\u002F ANTI-PATTERN: wrapping EVERY state update in startTransition \"just in case.\"\n\u002F\u002F startTransition adds overhead (tracking the transition scope). For cheap\n\u002F\u002F updates (toggling a boolean), it's unnecessary — the render is fast enough\n\u002F\u002F that it never needs to be interrupted. Reserve for MEASURABLY expensive renders.\n\n\u002F\u002F ANTI-PATTERN: using useDeferredValue on a value that never changes rapidly.\n\u002F\u002F If the value updates at most once per user action (not per keystroke),\n\u002F\u002F there's nothing to defer — useDeferredValue just adds overhead.\n",[30,3532,3533,3538,3543,3548,3553,3558,3563,3568,3573,3578,3582,3587,3592,3597,3602,3607,3612,3616,3621,3626,3631,3636,3640,3645,3650],{"__ignoreMap":28},[33,3534,3535],{"class":35,"line":36},[33,3536,3537],{"class":64},"\u002F\u002F CONCURRENT HELPS when:\n",[33,3539,3540],{"class":35,"line":54},[33,3541,3542],{"class":64},"\u002F\u002F 1. An expensive render would block urgent updates (typing, clicks)\n",[33,3544,3545],{"class":35,"line":61},[33,3546,3547],{"class":64},"\u002F\u002F    -> useTransition\u002FuseDeferredValue lets urgent updates interrupt\n",[33,3549,3550],{"class":35,"line":68},[33,3551,3552],{"class":64},"\u002F\u002F 2. Multiple state updates happen in the same tick\n",[33,3554,3555],{"class":35,"line":74},[33,3556,3557],{"class":64},"\u002F\u002F    -> automatic batching reduces re-renders (no code change needed)\n",[33,3559,3560],{"class":35,"line":80},[33,3561,3562],{"class":64},"\u002F\u002F 3. Parts of a page load data independently\n",[33,3564,3565],{"class":35,"line":86},[33,3566,3567],{"class":64},"\u002F\u002F    -> Suspense boundaries let fast sections appear before slow ones\n",[33,3569,3570],{"class":35,"line":91},[33,3571,3572],{"class":64},"\u002F\u002F 4. Tab\u002FRoute switching to heavy components\n",[33,3574,3575],{"class":35,"line":104},[33,3576,3577],{"class":64},"\u002F\u002F    -> useTransition keeps the current tab interactive while the new one renders\n",[33,3579,3580],{"class":35,"line":141},[33,3581,58],{"emptyLinePlaceholder":57},[33,3583,3584],{"class":35,"line":169},[33,3585,3586],{"class":64},"\u002F\u002F CONCURRENT DOESN'T HELP when:\n",[33,3588,3589],{"class":35,"line":174},[33,3590,3591],{"class":64},"\u002F\u002F 1. The render itself is cheap (\u003C 1ms) — there's nothing to interrupt\n",[33,3593,3594],{"class":35,"line":188},[33,3595,3596],{"class":64},"\u002F\u002F 2. There's only one state update — nothing to batch or prioritize\n",[33,3598,3599],{"class":35,"line":201},[33,3600,3601],{"class":64},"\u002F\u002F 3. The bottleneck is network, not rendering — Suspense shows a spinner\n",[33,3603,3604],{"class":35,"line":210},[33,3605,3606],{"class":64},"\u002F\u002F    either way; concurrent doesn't make the data arrive faster\n",[33,3608,3609],{"class":35,"line":222},[33,3610,3611],{"class":64},"\u002F\u002F 4. The component is impure — concurrent makes it WORSE, not better\n",[33,3613,3614],{"class":35,"line":234},[33,3615,58],{"emptyLinePlaceholder":57},[33,3617,3618],{"class":35,"line":240},[33,3619,3620],{"class":64},"\u002F\u002F ANTI-PATTERN: wrapping EVERY state update in startTransition \"just in case.\"\n",[33,3622,3623],{"class":35,"line":246},[33,3624,3625],{"class":64},"\u002F\u002F startTransition adds overhead (tracking the transition scope). For cheap\n",[33,3627,3628],{"class":35,"line":255},[33,3629,3630],{"class":64},"\u002F\u002F updates (toggling a boolean), it's unnecessary — the render is fast enough\n",[33,3632,3633],{"class":35,"line":267},[33,3634,3635],{"class":64},"\u002F\u002F that it never needs to be interrupted. Reserve for MEASURABLY expensive renders.\n",[33,3637,3638],{"class":35,"line":273},[33,3639,58],{"emptyLinePlaceholder":57},[33,3641,3642],{"class":35,"line":279},[33,3643,3644],{"class":64},"\u002F\u002F ANTI-PATTERN: using useDeferredValue on a value that never changes rapidly.\n",[33,3646,3647],{"class":35,"line":284},[33,3648,3649],{"class":64},"\u002F\u002F If the value updates at most once per user action (not per keystroke),\n",[33,3651,3652],{"class":35,"line":318},[33,3653,3654],{"class":64},"\u002F\u002F there's nothing to defer — useDeferredValue just adds overhead.\n",[14,3656,3658],{"id":3657},"complex-implementation-responsive-search-with-transitions","Complex Implementation — Responsive Search with Transitions",[19,3660,3662],{"filename":3661,"language":22},"complex_search.js",[24,3663,3665],{"className":26,"code":3664,"language":22,"meta":28,"style":28},"import { useState, useTransition, useDeferredValue, memo, useMemo } from 'react'\n\n\u002F\u002F A production-grade search that stays responsive on 50k+ items:\n\u002F\u002F - useTransition for the filter update (non-urgent)\n\u002F\u002F - isPending for visual feedback (dimming)\n\u002F\u002F - memo on the results list to skip re-renders when query is stale\n\nconst SearchResults = memo(function SearchResults({ items, filterText }) {\n  const filtered = useMemo(() => {\n    if (!filterText) return items\n    const lower = filterText.toLowerCase()\n    return items.filter(i =>\n      i.name.toLowerCase().includes(lower) ||\n      i.sku.toLowerCase().includes(lower)\n    )\n  }, [items, filterText])\n\n  return (\n    \u003Cdiv>\n      \u003Cp>{filtered.length} results\u003C\u002Fp>\n      \u003Cul>\n        {filtered.slice(0, 100).map(item => (\n          \u003Cli key={item.id}>{item.name} — {item.sku}\u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  )\n})\n\nfunction ProductSearch({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const [filterText, setFilterText] = useState('')\n  const [isPending, startTransition] = useTransition()\n\n  function handleChange(e) {\n    const value = e.target.value\n    setQuery(value)  \u002F\u002F URGENT: input updates immediately\n    startTransition(() => {\n      setFilterText(value)  \u002F\u002F NON-URGENT: results update when React is free\n    })\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cinput\n        value={query}\n        onChange={handleChange}\n        placeholder=\"Search 50k products…\"\n      \u002F>\n      \u003Cdiv style={{ opacity: isPending ? 0.7 : 1, transition: 'opacity 0.15s' }}>\n        \u003CSearchResults items={allProducts} filterText={filterText} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F ALTERNATIVE using useDeferredValue (same result, different approach):\nfunction ProductSearchDeferred({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const deferredQuery = useDeferredValue(query)\n  const isStale = query !== deferredQuery\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={e => setQuery(e.target.value)} \u002F>\n      \u003Cdiv style={{ opacity: isStale ? 0.7 : 1 }}>\n        \u003CSearchResults items={allProducts} filterText={deferredQuery} \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,3666,3667,3678,3682,3687,3692,3697,3702,3706,3733,3749,3767,3782,3796,3813,3827,3832,3837,3841,3847,3855,3874,3882,3913,3931,3936,3944,3952,3956,3960,3964,3976,4000,4025,4045,4049,4061,4071,4080,4090,4100,4105,4110,4115,4122,4131,4139,4150,4161,4172,4178,4208,4230,4239,4248,4253,4258,4263,4269,4283,4308,4321,4337,4342,4349,4358,4385,4408,4428,4437,4446,4451],{"__ignoreMap":28},[33,3668,3669,3671,3674,3676],{"class":35,"line":36},[33,3670,40],{"class":39},[33,3672,3673],{"class":43}," { useState, useTransition, useDeferredValue, memo, useMemo } ",[33,3675,47],{"class":39},[33,3677,51],{"class":50},[33,3679,3680],{"class":35,"line":54},[33,3681,58],{"emptyLinePlaceholder":57},[33,3683,3684],{"class":35,"line":61},[33,3685,3686],{"class":64},"\u002F\u002F A production-grade search that stays responsive on 50k+ items:\n",[33,3688,3689],{"class":35,"line":68},[33,3690,3691],{"class":64},"\u002F\u002F - useTransition for the filter update (non-urgent)\n",[33,3693,3694],{"class":35,"line":74},[33,3695,3696],{"class":64},"\u002F\u002F - isPending for visual feedback (dimming)\n",[33,3698,3699],{"class":35,"line":80},[33,3700,3701],{"class":64},"\u002F\u002F - memo on the results list to skip re-renders when query is stale\n",[33,3703,3704],{"class":35,"line":86},[33,3705,58],{"emptyLinePlaceholder":57},[33,3707,3708,3710,3712,3714,3716,3718,3720,3722,3724,3726,3728,3731],{"class":35,"line":91},[33,3709,1441],{"class":39},[33,3711,2179],{"class":113},[33,3713,601],{"class":39},[33,3715,2184],{"class":97},[33,3717,132],{"class":43},[33,3719,94],{"class":39},[33,3721,2179],{"class":97},[33,3723,515],{"class":43},[33,3725,2199],{"class":386},[33,3727,117],{"class":43},[33,3729,3730],{"class":386},"filterText",[33,3732,521],{"class":43},[33,3734,3735,3737,3739,3741,3743,3745,3747],{"class":35,"line":104},[33,3736,107],{"class":39},[33,3738,1911],{"class":113},[33,3740,601],{"class":39},[33,3742,1916],{"class":97},[33,3744,398],{"class":43},[33,3746,401],{"class":39},[33,3748,207],{"class":43},[33,3750,3751,3754,3756,3758,3761,3764],{"class":35,"line":141},[33,3752,3753],{"class":39},"    if",[33,3755,3395],{"class":43},[33,3757,3398],{"class":39},[33,3759,3760],{"class":43},"filterText) ",[33,3762,3763],{"class":39},"return",[33,3765,3766],{"class":43}," items\n",[33,3768,3769,3771,3773,3775,3778,3780],{"class":35,"line":169},[33,3770,595],{"class":39},[33,3772,2239],{"class":113},[33,3774,601],{"class":39},[33,3776,3777],{"class":43}," filterText.",[33,3779,663],{"class":97},[33,3781,219],{"class":43},[33,3783,3784,3786,3788,3790,3792,3794],{"class":35,"line":174},[33,3785,2253],{"class":39},[33,3787,2256],{"class":43},[33,3789,647],{"class":97},[33,3791,132],{"class":43},[33,3793,2263],{"class":386},[33,3795,655],{"class":39},[33,3797,3798,3801,3803,3805,3807,3810],{"class":35,"line":188},[33,3799,3800],{"class":43},"      i.name.",[33,3802,663],{"class":97},[33,3804,666],{"class":43},[33,3806,669],{"class":97},[33,3808,3809],{"class":43},"(lower) ",[33,3811,3812],{"class":39},"||\n",[33,3814,3815,3818,3820,3822,3824],{"class":35,"line":201},[33,3816,3817],{"class":43},"      i.sku.",[33,3819,663],{"class":97},[33,3821,666],{"class":43},[33,3823,669],{"class":97},[33,3825,3826],{"class":43},"(lower)\n",[33,3828,3829],{"class":35,"line":210},[33,3830,3831],{"class":43},"    )\n",[33,3833,3834],{"class":35,"line":222},[33,3835,3836],{"class":43},"  }, [items, filterText])\n",[33,3838,3839],{"class":35,"line":234},[33,3840,58],{"emptyLinePlaceholder":57},[33,3842,3843,3845],{"class":35,"line":240},[33,3844,287],{"class":39},[33,3846,703],{"class":43},[33,3848,3849,3851,3853],{"class":35,"line":246},[33,3850,709],{"class":43},[33,3852,712],{"class":293},[33,3854,315],{"class":43},[33,3856,3857,3859,3861,3864,3867,3870,3872],{"class":35,"line":255},[33,3858,720],{"class":43},[33,3860,652],{"class":293},[33,3862,3863],{"class":43},">{filtered.",[33,3865,3866],{"class":113},"length",[33,3868,3869],{"class":43},"} results\u003C\u002F",[33,3871,652],{"class":293},[33,3873,315],{"class":43},[33,3875,3876,3878,3880],{"class":35,"line":267},[33,3877,720],{"class":43},[33,3879,2299],{"class":293},[33,3881,315],{"class":43},[33,3883,3884,3887,3890,3892,3895,3897,3900,3902,3904,3906,3909,3911],{"class":35,"line":273},[33,3885,3886],{"class":43},"        {filtered.",[33,3888,3889],{"class":97},"slice",[33,3891,132],{"class":43},[33,3893,3894],{"class":113},"0",[33,3896,117],{"class":43},[33,3898,3899],{"class":113},"100",[33,3901,3037],{"class":43},[33,3903,2309],{"class":97},[33,3905,132],{"class":43},[33,3907,3908],{"class":386},"item",[33,3910,1021],{"class":39},[33,3912,703],{"class":43},[33,3914,3915,3918,3920,3922,3924,3927,3929],{"class":35,"line":279},[33,3916,3917],{"class":43},"          \u003C",[33,3919,2321],{"class":293},[33,3921,2324],{"class":97},[33,3923,126],{"class":39},[33,3925,3926],{"class":43},"{item.id}>{item.name} — {item.sku}\u003C\u002F",[33,3928,2321],{"class":293},[33,3930,315],{"class":43},[33,3932,3933],{"class":35,"line":284},[33,3934,3935],{"class":43},"        ))}\n",[33,3937,3938,3940,3942],{"class":35,"line":318},[33,3939,1351],{"class":43},[33,3941,2299],{"class":293},[33,3943,315],{"class":43},[33,3945,3946,3948,3950],{"class":35,"line":698},[33,3947,760],{"class":43},[33,3949,712],{"class":293},[33,3951,315],{"class":43},[33,3953,3954],{"class":35,"line":706},[33,3955,770],{"class":43},[33,3957,3958],{"class":35,"line":717},[33,3959,853],{"class":43},[33,3961,3962],{"class":35,"line":741},[33,3963,58],{"emptyLinePlaceholder":57},[33,3965,3966,3968,3970,3972,3974],{"class":35,"line":757},[33,3967,94],{"class":39},[33,3969,512],{"class":97},[33,3971,515],{"class":43},[33,3973,518],{"class":386},[33,3975,521],{"class":43},[33,3977,3978,3980,3982,3984,3986,3988,3990,3992,3994,3996,3998],{"class":35,"line":767},[33,3979,107],{"class":39},[33,3981,110],{"class":43},[33,3983,530],{"class":113},[33,3985,117],{"class":43},[33,3987,535],{"class":113},[33,3989,123],{"class":43},[33,3991,126],{"class":39},[33,3993,129],{"class":97},[33,3995,132],{"class":43},[33,3997,546],{"class":50},[33,3999,138],{"class":43},[33,4001,4002,4004,4006,4008,4010,4013,4015,4017,4019,4021,4023],{"class":35,"line":773},[33,4003,107],{"class":39},[33,4005,110],{"class":43},[33,4007,3730],{"class":113},[33,4009,117],{"class":43},[33,4011,4012],{"class":113},"setFilterText",[33,4014,123],{"class":43},[33,4016,126],{"class":39},[33,4018,129],{"class":97},[33,4020,132],{"class":43},[33,4022,546],{"class":50},[33,4024,138],{"class":43},[33,4026,4027,4029,4031,4033,4035,4037,4039,4041,4043],{"class":35,"line":778},[33,4028,107],{"class":39},[33,4030,110],{"class":43},[33,4032,1148],{"class":113},[33,4034,117],{"class":43},[33,4036,826],{"class":113},[33,4038,123],{"class":43},[33,4040,126],{"class":39},[33,4042,1159],{"class":97},[33,4044,219],{"class":43},[33,4046,4047],{"class":35,"line":783},[33,4048,58],{"emptyLinePlaceholder":57},[33,4050,4051,4053,4055,4057,4059],{"class":35,"line":789},[33,4052,580],{"class":39},[33,4054,583],{"class":97},[33,4056,132],{"class":43},[33,4058,588],{"class":386},[33,4060,390],{"class":43},[33,4062,4063,4065,4067,4069],{"class":35,"line":795},[33,4064,595],{"class":39},[33,4066,598],{"class":113},[33,4068,601],{"class":39},[33,4070,604],{"class":43},[33,4072,4073,4075,4077],{"class":35,"line":801},[33,4074,609],{"class":97},[33,4076,612],{"class":43},[33,4078,4079],{"class":64},"\u002F\u002F URGENT: input updates immediately\n",[33,4081,4082,4084,4086,4088],{"class":35,"line":2122},[33,4083,620],{"class":97},[33,4085,398],{"class":43},[33,4087,401],{"class":39},[33,4089,207],{"class":43},[33,4091,4092,4095,4097],{"class":35,"line":2128},[33,4093,4094],{"class":97},"      setFilterText",[33,4096,612],{"class":43},[33,4098,4099],{"class":64},"\u002F\u002F NON-URGENT: results update when React is free\n",[33,4101,4103],{"class":35,"line":4102},40,[33,4104,687],{"class":43},[33,4106,4108],{"class":35,"line":4107},41,[33,4109,276],{"class":43},[33,4111,4113],{"class":35,"line":4112},42,[33,4114,58],{"emptyLinePlaceholder":57},[33,4116,4118,4120],{"class":35,"line":4117},43,[33,4119,287],{"class":39},[33,4121,703],{"class":43},[33,4123,4125,4127,4129],{"class":35,"line":4124},44,[33,4126,709],{"class":43},[33,4128,712],{"class":293},[33,4130,315],{"class":43},[33,4132,4134,4136],{"class":35,"line":4133},45,[33,4135,720],{"class":43},[33,4137,4138],{"class":293},"input\n",[33,4140,4142,4145,4147],{"class":35,"line":4141},46,[33,4143,4144],{"class":97},"        value",[33,4146,126],{"class":39},[33,4148,4149],{"class":43},"{query}\n",[33,4151,4153,4156,4158],{"class":35,"line":4152},47,[33,4154,4155],{"class":97},"        onChange",[33,4157,126],{"class":39},[33,4159,4160],{"class":43},"{handleChange}\n",[33,4162,4164,4167,4169],{"class":35,"line":4163},48,[33,4165,4166],{"class":97},"        placeholder",[33,4168,126],{"class":39},[33,4170,4171],{"class":50},"\"Search 50k products…\"\n",[33,4173,4175],{"class":35,"line":4174},49,[33,4176,4177],{"class":43},"      \u002F>\n",[33,4179,4181,4183,4185,4187,4189,4191,4193,4196,4198,4200,4203,4206],{"class":35,"line":4180},50,[33,4182,720],{"class":43},[33,4184,712],{"class":293},[33,4186,1313],{"class":97},[33,4188,126],{"class":39},[33,4190,1318],{"class":43},[33,4192,1321],{"class":39},[33,4194,4195],{"class":113}," 0.7",[33,4197,1327],{"class":39},[33,4199,1330],{"class":113},[33,4201,4202],{"class":43},", transition: ",[33,4204,4205],{"class":50},"'opacity 0.15s'",[33,4207,1333],{"class":43},[33,4209,4211,4213,4215,4218,4220,4223,4225,4227],{"class":35,"line":4210},51,[33,4212,1338],{"class":43},[33,4214,2454],{"class":113},[33,4216,4217],{"class":97}," items",[33,4219,126],{"class":39},[33,4221,4222],{"class":43},"{allProducts} ",[33,4224,3730],{"class":97},[33,4226,126],{"class":39},[33,4228,4229],{"class":43},"{filterText} \u002F>\n",[33,4231,4233,4235,4237],{"class":35,"line":4232},52,[33,4234,1351],{"class":43},[33,4236,712],{"class":293},[33,4238,315],{"class":43},[33,4240,4242,4244,4246],{"class":35,"line":4241},53,[33,4243,760],{"class":43},[33,4245,712],{"class":293},[33,4247,315],{"class":43},[33,4249,4251],{"class":35,"line":4250},54,[33,4252,770],{"class":43},[33,4254,4256],{"class":35,"line":4255},55,[33,4257,321],{"class":43},[33,4259,4261],{"class":35,"line":4260},56,[33,4262,58],{"emptyLinePlaceholder":57},[33,4264,4266],{"class":35,"line":4265},57,[33,4267,4268],{"class":64},"\u002F\u002F ALTERNATIVE using useDeferredValue (same result, different approach):\n",[33,4270,4272,4274,4277,4279,4281],{"class":35,"line":4271},58,[33,4273,94],{"class":39},[33,4275,4276],{"class":97}," ProductSearchDeferred",[33,4278,515],{"class":43},[33,4280,518],{"class":386},[33,4282,521],{"class":43},[33,4284,4286,4288,4290,4292,4294,4296,4298,4300,4302,4304,4306],{"class":35,"line":4285},59,[33,4287,107],{"class":39},[33,4289,110],{"class":43},[33,4291,530],{"class":113},[33,4293,117],{"class":43},[33,4295,535],{"class":113},[33,4297,123],{"class":43},[33,4299,126],{"class":39},[33,4301,129],{"class":97},[33,4303,132],{"class":43},[33,4305,546],{"class":50},[33,4307,138],{"class":43},[33,4309,4311,4313,4315,4317,4319],{"class":35,"line":4310},60,[33,4312,107],{"class":39},[33,4314,1877],{"class":113},[33,4316,601],{"class":39},[33,4318,1882],{"class":97},[33,4320,1885],{"class":43},[33,4322,4324,4326,4328,4330,4332,4334],{"class":35,"line":4323},61,[33,4325,107],{"class":39},[33,4327,1980],{"class":113},[33,4329,601],{"class":39},[33,4331,1985],{"class":43},[33,4333,1988],{"class":39},[33,4335,4336],{"class":43}," deferredQuery\n",[33,4338,4340],{"class":35,"line":4339},62,[33,4341,58],{"emptyLinePlaceholder":57},[33,4343,4345,4347],{"class":35,"line":4344},63,[33,4346,287],{"class":39},[33,4348,703],{"class":43},[33,4350,4352,4354,4356],{"class":35,"line":4351},64,[33,4353,709],{"class":43},[33,4355,712],{"class":293},[33,4357,315],{"class":43},[33,4359,4361,4363,4365,4367,4369,4371,4373,4375,4377,4379,4381,4383],{"class":35,"line":4360},65,[33,4362,720],{"class":43},[33,4364,723],{"class":293},[33,4366,598],{"class":97},[33,4368,126],{"class":39},[33,4370,730],{"class":43},[33,4372,733],{"class":97},[33,4374,126],{"class":39},[33,4376,2031],{"class":43},[33,4378,588],{"class":386},[33,4380,1021],{"class":39},[33,4382,2038],{"class":97},[33,4384,2041],{"class":43},[33,4386,4388,4390,4392,4394,4396,4398,4400,4402,4404,4406],{"class":35,"line":4387},66,[33,4389,720],{"class":43},[33,4391,712],{"class":293},[33,4393,1313],{"class":97},[33,4395,126],{"class":39},[33,4397,2054],{"class":43},[33,4399,1321],{"class":39},[33,4401,4195],{"class":113},[33,4403,1327],{"class":39},[33,4405,1330],{"class":113},[33,4407,1333],{"class":43},[33,4409,4411,4413,4415,4417,4419,4421,4423,4425],{"class":35,"line":4410},67,[33,4412,1338],{"class":43},[33,4414,2454],{"class":113},[33,4416,4217],{"class":97},[33,4418,126],{"class":39},[33,4420,4222],{"class":43},[33,4422,3730],{"class":97},[33,4424,126],{"class":39},[33,4426,4427],{"class":43},"{deferredQuery} \u002F>\n",[33,4429,4431,4433,4435],{"class":35,"line":4430},68,[33,4432,1351],{"class":43},[33,4434,712],{"class":293},[33,4436,315],{"class":43},[33,4438,4440,4442,4444],{"class":35,"line":4439},69,[33,4441,760],{"class":43},[33,4443,712],{"class":293},[33,4445,315],{"class":43},[33,4447,4449],{"class":35,"line":4448},70,[33,4450,770],{"class":43},[33,4452,4454],{"class":35,"line":4453},71,[33,4455,321],{"class":43},[14,4457,4459],{"id":4458},"tips-tricks","💡 Tips & Tricks",[19,4461,4463],{"filename":4462,"language":22},"tips.js",[24,4464,4466],{"className":26,"code":4465,"language":22,"meta":28,"style":28},"\u002F\u002F [Idiom] Reach for startTransition specifically for updates that are\n\u002F\u002F expensive to render but not urgent to feel instantaneous (re-filtering\n\u002F\u002F a large list, re-computing a chart) — leave genuinely urgent updates\n\u002F\u002F (text typed into an input) as regular, unwrapped setState calls.\n\n\u002F\u002F [Idiom] Choose useDeferredValue over startTransition when you receive a\n\u002F\u002F value you don't control the setter for (a prop, a third-party hook's\n\u002F\u002F return value); choose startTransition when you do own the setState call.\n\n\u002F\u002F [Debug] If automatic batching in React 18 changes behavior that pre-18\n\u002F\u002F code relied on (an intermediate render between two setState calls in an\n\u002F\u002F async callback), the discouraged escape hatch is flushSync from react-dom.\n\n\u002F\u002F [Performance] isPending from useTransition is a better UX signal than a\n\u002F\u002F manually-tracked isFiltering boolean — it's wired directly into React's\n\u002F\u002F own scheduling, accurately reflecting when the deferred work is in flight.\n\n\u002F\u002F [Idiom] Treat \"components must be pure\" as a HARD requirement, not a style\n\u002F\u002F guideline, once concurrent features are in play — a component with\n\u002F\u002F render-time side effects can produce subtly wrong behavior (double-counted\n\u002F\u002F effects, stale closures over mutated external variables) specifically\n\u002F\u002F because React may render it speculatively and discard the result.\n\n\u002F\u002F [Performance] Don't wrap every setState in startTransition — it adds\n\u002F\u002F overhead for cheap renders that never need interruption. Reserve for\n\u002F\u002F MEASURABLY expensive renders confirmed via profiling.\n",[30,4467,4468,4473,4478,4483,4488,4492,4497,4502,4507,4511,4516,4521,4526,4530,4535,4540,4545,4549,4554,4559,4564,4569,4574,4578,4583,4588],{"__ignoreMap":28},[33,4469,4470],{"class":35,"line":36},[33,4471,4472],{"class":64},"\u002F\u002F [Idiom] Reach for startTransition specifically for updates that are\n",[33,4474,4475],{"class":35,"line":54},[33,4476,4477],{"class":64},"\u002F\u002F expensive to render but not urgent to feel instantaneous (re-filtering\n",[33,4479,4480],{"class":35,"line":61},[33,4481,4482],{"class":64},"\u002F\u002F a large list, re-computing a chart) — leave genuinely urgent updates\n",[33,4484,4485],{"class":35,"line":68},[33,4486,4487],{"class":64},"\u002F\u002F (text typed into an input) as regular, unwrapped setState calls.\n",[33,4489,4490],{"class":35,"line":74},[33,4491,58],{"emptyLinePlaceholder":57},[33,4493,4494],{"class":35,"line":80},[33,4495,4496],{"class":64},"\u002F\u002F [Idiom] Choose useDeferredValue over startTransition when you receive a\n",[33,4498,4499],{"class":35,"line":86},[33,4500,4501],{"class":64},"\u002F\u002F value you don't control the setter for (a prop, a third-party hook's\n",[33,4503,4504],{"class":35,"line":91},[33,4505,4506],{"class":64},"\u002F\u002F return value); choose startTransition when you do own the setState call.\n",[33,4508,4509],{"class":35,"line":104},[33,4510,58],{"emptyLinePlaceholder":57},[33,4512,4513],{"class":35,"line":141},[33,4514,4515],{"class":64},"\u002F\u002F [Debug] If automatic batching in React 18 changes behavior that pre-18\n",[33,4517,4518],{"class":35,"line":169},[33,4519,4520],{"class":64},"\u002F\u002F code relied on (an intermediate render between two setState calls in an\n",[33,4522,4523],{"class":35,"line":174},[33,4524,4525],{"class":64},"\u002F\u002F async callback), the discouraged escape hatch is flushSync from react-dom.\n",[33,4527,4528],{"class":35,"line":188},[33,4529,58],{"emptyLinePlaceholder":57},[33,4531,4532],{"class":35,"line":201},[33,4533,4534],{"class":64},"\u002F\u002F [Performance] isPending from useTransition is a better UX signal than a\n",[33,4536,4537],{"class":35,"line":210},[33,4538,4539],{"class":64},"\u002F\u002F manually-tracked isFiltering boolean — it's wired directly into React's\n",[33,4541,4542],{"class":35,"line":222},[33,4543,4544],{"class":64},"\u002F\u002F own scheduling, accurately reflecting when the deferred work is in flight.\n",[33,4546,4547],{"class":35,"line":234},[33,4548,58],{"emptyLinePlaceholder":57},[33,4550,4551],{"class":35,"line":240},[33,4552,4553],{"class":64},"\u002F\u002F [Idiom] Treat \"components must be pure\" as a HARD requirement, not a style\n",[33,4555,4556],{"class":35,"line":246},[33,4557,4558],{"class":64},"\u002F\u002F guideline, once concurrent features are in play — a component with\n",[33,4560,4561],{"class":35,"line":255},[33,4562,4563],{"class":64},"\u002F\u002F render-time side effects can produce subtly wrong behavior (double-counted\n",[33,4565,4566],{"class":35,"line":267},[33,4567,4568],{"class":64},"\u002F\u002F effects, stale closures over mutated external variables) specifically\n",[33,4570,4571],{"class":35,"line":273},[33,4572,4573],{"class":64},"\u002F\u002F because React may render it speculatively and discard the result.\n",[33,4575,4576],{"class":35,"line":279},[33,4577,58],{"emptyLinePlaceholder":57},[33,4579,4580],{"class":35,"line":284},[33,4581,4582],{"class":64},"\u002F\u002F [Performance] Don't wrap every setState in startTransition — it adds\n",[33,4584,4585],{"class":35,"line":318},[33,4586,4587],{"class":64},"\u002F\u002F overhead for cheap renders that never need interruption. Reserve for\n",[33,4589,4590],{"class":35,"line":698},[33,4591,4592],{"class":64},"\u002F\u002F MEASURABLY expensive renders confirmed via profiling.\n",[14,4594,4596],{"id":4595},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,4598,4600],{"filename":4599,"language":22},"edge_cases.js",[24,4601,4603],{"className":26,"code":4602,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] Automatic batching is a genuine behavioral change from React 17\n\u002F\u002F and earlier, not just an optimization. Code that relied on synchronous,\n\u002F\u002F unbatched setState inside a setTimeout\u002Fpromise\u002Fnative-event callback\n\u002F\u002F behaves differently. flushSync is the explicit opt-out for rare cases.\n\n\u002F\u002F [Gotcha] startTransition's callback must call setState SYNCHRONOUSLY.\n\u002F\u002F Wrapping an async function or using `await` inside the callback doesn't\n\u002F\u002F work — only state updates that happen synchronously within the transition\n\u002F\u002F are marked low-priority. Anything after `await` runs outside the marking.\n\n\u002F\u002F [Gotcha] A transition update can be INTERRUPTED and its render thrown away\n\u002F\u002F entirely if a more urgent update comes in. This is intentional (keeps input\n\u002F\u002F responsive during rapid typing), but it means a transition's side effects\n\u002F\u002F (if any existed, which they shouldn't per the purity requirement) could\n\u002F\u002F appear to run inconsistently.\n\n\u002F\u002F [Gotcha] useDeferredValue does NOTHING useful on a value's very first render.\n\u002F\u002F On initial mount, deferredValue equals value immediately; the \"lagging\"\n\u002F\u002F behavior only manifests on subsequent updates once there's a previous value\n\u002F\u002F to lag behind. Testing it against a component's first render looks broken.\n\n\u002F\u002F [Gotcha] Suspense-for-data has real integration requirements (throwing a\n\u002F\u002F promise from render, caching resolved values across attempts) that hand-\n\u002F\u002F written fetch calls do not satisfy without a library's support. Throwing a\n\u002F\u002F raw, uncached promise from a component body produces an infinite re-render\n\u002F\u002F loop, not a working suspense boundary.\n\n\u002F\u002F [Gotcha] useTransition's isPending stays true from the moment startTransition\n\u002F\u002F is called until the transition's update COMMITS (not just until it starts\n\u002F\u002F rendering). For very fast renders, this window is imperceptible — but if\n\u002F\u002F you use isPending to show a loading spinner, it may flash too briefly.\n\n\u002F\u002F [Gotcha] Concurrent rendering can cause useEffect to run in a different\n\u002F\u002F order than expected if React renders speculatively and discards. Effects\n\u002F\u002F only run after a commit, but the commit order can differ from render order\n\u002F\u002F when concurrent features interrupt and restart rendering.\n",[30,4604,4605,4610,4615,4620,4625,4629,4634,4639,4644,4649,4653,4658,4663,4668,4673,4678,4682,4687,4692,4697,4702,4706,4711,4716,4721,4726,4731,4735,4740,4745,4750,4755,4759,4764,4769,4774],{"__ignoreMap":28},[33,4606,4607],{"class":35,"line":36},[33,4608,4609],{"class":64},"\u002F\u002F [Gotcha] Automatic batching is a genuine behavioral change from React 17\n",[33,4611,4612],{"class":35,"line":54},[33,4613,4614],{"class":64},"\u002F\u002F and earlier, not just an optimization. Code that relied on synchronous,\n",[33,4616,4617],{"class":35,"line":61},[33,4618,4619],{"class":64},"\u002F\u002F unbatched setState inside a setTimeout\u002Fpromise\u002Fnative-event callback\n",[33,4621,4622],{"class":35,"line":68},[33,4623,4624],{"class":64},"\u002F\u002F behaves differently. flushSync is the explicit opt-out for rare cases.\n",[33,4626,4627],{"class":35,"line":74},[33,4628,58],{"emptyLinePlaceholder":57},[33,4630,4631],{"class":35,"line":80},[33,4632,4633],{"class":64},"\u002F\u002F [Gotcha] startTransition's callback must call setState SYNCHRONOUSLY.\n",[33,4635,4636],{"class":35,"line":86},[33,4637,4638],{"class":64},"\u002F\u002F Wrapping an async function or using `await` inside the callback doesn't\n",[33,4640,4641],{"class":35,"line":91},[33,4642,4643],{"class":64},"\u002F\u002F work — only state updates that happen synchronously within the transition\n",[33,4645,4646],{"class":35,"line":104},[33,4647,4648],{"class":64},"\u002F\u002F are marked low-priority. Anything after `await` runs outside the marking.\n",[33,4650,4651],{"class":35,"line":141},[33,4652,58],{"emptyLinePlaceholder":57},[33,4654,4655],{"class":35,"line":169},[33,4656,4657],{"class":64},"\u002F\u002F [Gotcha] A transition update can be INTERRUPTED and its render thrown away\n",[33,4659,4660],{"class":35,"line":174},[33,4661,4662],{"class":64},"\u002F\u002F entirely if a more urgent update comes in. This is intentional (keeps input\n",[33,4664,4665],{"class":35,"line":188},[33,4666,4667],{"class":64},"\u002F\u002F responsive during rapid typing), but it means a transition's side effects\n",[33,4669,4670],{"class":35,"line":201},[33,4671,4672],{"class":64},"\u002F\u002F (if any existed, which they shouldn't per the purity requirement) could\n",[33,4674,4675],{"class":35,"line":210},[33,4676,4677],{"class":64},"\u002F\u002F appear to run inconsistently.\n",[33,4679,4680],{"class":35,"line":222},[33,4681,58],{"emptyLinePlaceholder":57},[33,4683,4684],{"class":35,"line":234},[33,4685,4686],{"class":64},"\u002F\u002F [Gotcha] useDeferredValue does NOTHING useful on a value's very first render.\n",[33,4688,4689],{"class":35,"line":240},[33,4690,4691],{"class":64},"\u002F\u002F On initial mount, deferredValue equals value immediately; the \"lagging\"\n",[33,4693,4694],{"class":35,"line":246},[33,4695,4696],{"class":64},"\u002F\u002F behavior only manifests on subsequent updates once there's a previous value\n",[33,4698,4699],{"class":35,"line":255},[33,4700,4701],{"class":64},"\u002F\u002F to lag behind. Testing it against a component's first render looks broken.\n",[33,4703,4704],{"class":35,"line":267},[33,4705,58],{"emptyLinePlaceholder":57},[33,4707,4708],{"class":35,"line":273},[33,4709,4710],{"class":64},"\u002F\u002F [Gotcha] Suspense-for-data has real integration requirements (throwing a\n",[33,4712,4713],{"class":35,"line":279},[33,4714,4715],{"class":64},"\u002F\u002F promise from render, caching resolved values across attempts) that hand-\n",[33,4717,4718],{"class":35,"line":284},[33,4719,4720],{"class":64},"\u002F\u002F written fetch calls do not satisfy without a library's support. Throwing a\n",[33,4722,4723],{"class":35,"line":318},[33,4724,4725],{"class":64},"\u002F\u002F raw, uncached promise from a component body produces an infinite re-render\n",[33,4727,4728],{"class":35,"line":698},[33,4729,4730],{"class":64},"\u002F\u002F loop, not a working suspense boundary.\n",[33,4732,4733],{"class":35,"line":706},[33,4734,58],{"emptyLinePlaceholder":57},[33,4736,4737],{"class":35,"line":717},[33,4738,4739],{"class":64},"\u002F\u002F [Gotcha] useTransition's isPending stays true from the moment startTransition\n",[33,4741,4742],{"class":35,"line":741},[33,4743,4744],{"class":64},"\u002F\u002F is called until the transition's update COMMITS (not just until it starts\n",[33,4746,4747],{"class":35,"line":757},[33,4748,4749],{"class":64},"\u002F\u002F rendering). For very fast renders, this window is imperceptible — but if\n",[33,4751,4752],{"class":35,"line":767},[33,4753,4754],{"class":64},"\u002F\u002F you use isPending to show a loading spinner, it may flash too briefly.\n",[33,4756,4757],{"class":35,"line":773},[33,4758,58],{"emptyLinePlaceholder":57},[33,4760,4761],{"class":35,"line":778},[33,4762,4763],{"class":64},"\u002F\u002F [Gotcha] Concurrent rendering can cause useEffect to run in a different\n",[33,4765,4766],{"class":35,"line":783},[33,4767,4768],{"class":64},"\u002F\u002F order than expected if React renders speculatively and discards. Effects\n",[33,4770,4771],{"class":35,"line":789},[33,4772,4773],{"class":64},"\u002F\u002F only run after a commit, but the commit order can differ from render order\n",[33,4775,4776],{"class":35,"line":795},[33,4777,4778],{"class":64},"\u002F\u002F when concurrent features interrupt and restart rendering.\n",[14,4780,4782],{"id":4781},"spot-the-bug","🧠 Spot the Bug",[652,4784,4785,4786,4788],{},"A developer wraps an expensive search-filtering update in ",[30,4787,826],{}," to keep the input responsive, but the input still visibly stutters while typing quickly.",[19,4790,4792],{"filename":4791,"language":22},"spot_the_bug.js",[24,4793,4795],{"className":26,"code":4794,"language":22,"meta":28,"style":28},"function ProductSearch({ allProducts }) {\n  const [query, setQuery] = useState('')\n  const [filtered, setFiltered] = useState(allProducts)\n\n  async function handleChange(e) {\n    const value = e.target.value\n\n    startTransition(async () => {\n      setQuery(value)\n      const results = await runExpensiveFilter(allProducts, value)\n      setFiltered(results)\n    })\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      \u003CProductGrid products={filtered} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,4796,4797,4809,4833,4853,4857,4871,4881,4885,4899,4906,4921,4928,4932,4936,4940,4946,4954,4972,4984,4992,4996],{"__ignoreMap":28},[33,4798,4799,4801,4803,4805,4807],{"class":35,"line":36},[33,4800,94],{"class":39},[33,4802,512],{"class":97},[33,4804,515],{"class":43},[33,4806,518],{"class":386},[33,4808,521],{"class":43},[33,4810,4811,4813,4815,4817,4819,4821,4823,4825,4827,4829,4831],{"class":35,"line":54},[33,4812,107],{"class":39},[33,4814,110],{"class":43},[33,4816,530],{"class":113},[33,4818,117],{"class":43},[33,4820,535],{"class":113},[33,4822,123],{"class":43},[33,4824,126],{"class":39},[33,4826,129],{"class":97},[33,4828,132],{"class":43},[33,4830,546],{"class":50},[33,4832,138],{"class":43},[33,4834,4835,4837,4839,4841,4843,4845,4847,4849,4851],{"class":35,"line":61},[33,4836,107],{"class":39},[33,4838,110],{"class":43},[33,4840,557],{"class":113},[33,4842,117],{"class":43},[33,4844,562],{"class":113},[33,4846,123],{"class":43},[33,4848,126],{"class":39},[33,4850,129],{"class":97},[33,4852,571],{"class":43},[33,4854,4855],{"class":35,"line":68},[33,4856,58],{"emptyLinePlaceholder":57},[33,4858,4859,4861,4863,4865,4867,4869],{"class":35,"line":74},[33,4860,177],{"class":39},[33,4862,180],{"class":39},[33,4864,583],{"class":97},[33,4866,132],{"class":43},[33,4868,588],{"class":386},[33,4870,390],{"class":43},[33,4872,4873,4875,4877,4879],{"class":35,"line":80},[33,4874,595],{"class":39},[33,4876,598],{"class":113},[33,4878,601],{"class":39},[33,4880,604],{"class":43},[33,4882,4883],{"class":35,"line":86},[33,4884,58],{"emptyLinePlaceholder":57},[33,4886,4887,4889,4891,4893,4895,4897],{"class":35,"line":91},[33,4888,620],{"class":97},[33,4890,132],{"class":43},[33,4892,881],{"class":39},[33,4894,884],{"class":43},[33,4896,401],{"class":39},[33,4898,207],{"class":43},[33,4900,4901,4904],{"class":35,"line":104},[33,4902,4903],{"class":97},"      setQuery",[33,4905,1002],{"class":43},[33,4907,4908,4911,4913,4915,4917,4919],{"class":35,"line":141},[33,4909,4910],{"class":39},"      const",[33,4912,905],{"class":113},[33,4914,601],{"class":39},[33,4916,910],{"class":39},[33,4918,913],{"class":97},[33,4920,916],{"class":43},[33,4922,4923,4925],{"class":35,"line":169},[33,4924,641],{"class":97},[33,4926,4927],{"class":43},"(results)\n",[33,4929,4930],{"class":35,"line":174},[33,4931,687],{"class":43},[33,4933,4934],{"class":35,"line":188},[33,4935,276],{"class":43},[33,4937,4938],{"class":35,"line":201},[33,4939,58],{"emptyLinePlaceholder":57},[33,4941,4942,4944],{"class":35,"line":210},[33,4943,287],{"class":39},[33,4945,703],{"class":43},[33,4947,4948,4950,4952],{"class":35,"line":222},[33,4949,709],{"class":43},[33,4951,712],{"class":293},[33,4953,315],{"class":43},[33,4955,4956,4958,4960,4962,4964,4966,4968,4970],{"class":35,"line":234},[33,4957,720],{"class":43},[33,4959,723],{"class":293},[33,4961,598],{"class":97},[33,4963,126],{"class":39},[33,4965,730],{"class":43},[33,4967,733],{"class":97},[33,4969,126],{"class":39},[33,4971,738],{"class":43},[33,4973,4974,4976,4978,4980,4982],{"class":35,"line":240},[33,4975,720],{"class":43},[33,4977,746],{"class":113},[33,4979,749],{"class":97},[33,4981,126],{"class":39},[33,4983,754],{"class":43},[33,4985,4986,4988,4990],{"class":35,"line":246},[33,4987,760],{"class":43},[33,4989,712],{"class":293},[33,4991,315],{"class":43},[33,4993,4994],{"class":35,"line":255},[33,4995,770],{"class":43},[33,4997,4998],{"class":35,"line":267},[33,4999,321],{"class":43},[5001,5002,5003,5007,5010,5051,5057,5147],"details",{},[5004,5005,5006],"summary",{},"Answer",[652,5008,5009],{},"Two problems compound here:",[5011,5012,5013,5027],"ol",{},[2321,5014,5015,5018,5019,5023,5024,5026],{},[30,5016,5017],{},"setQuery(value)"," — the update that needs to feel instantaneous — is placed ",[5020,5021,5022],"strong",{},"inside"," the ",[30,5025,826],{}," callback, marking it low-priority along with the expensive filter. The input itself now competes for priority instead of being exempted from it — exactly backwards from the intent.",[2321,5028,5029,5030,5032,5033,5035,5036,5039,5040,5043,5044,5046,5047,5050],{},"The callback passed to ",[30,5031,826],{}," is ",[30,5034,881],{},", and only the ",[5020,5037,5038],{},"synchronous portion"," of a transition callback is actually tracked as part of the transition. ",[30,5041,5042],{},"await runExpensiveFilter(...)"," and the ",[30,5045,562],{}," call after it run ",[5020,5048,5049],{},"outside"," the transition's low-priority marking entirely, once the microtask queue resumes them — defeating the purpose a second time over.",[652,5052,5053,5056],{},[5020,5054,5055],{},"Fix",": keep urgent updates outside the transition, keep the transition callback synchronous, and perform async work outside then mark only the state update:",[24,5058,5060],{"className":26,"code":5059,"language":22,"meta":28,"style":28},"function handleChange(e) {\n  const value = e.target.value\n  setQuery(value)  \u002F\u002F urgent — OUTSIDE the transition\n  startTransition(() => {\n    setFiltered(allProducts.filter(p => p.name.toLowerCase().includes(value.toLowerCase())))\n    \u002F\u002F synchronous filter — stays inside the transition\n  })\n}\n",[30,5061,5062,5074,5084,5093,5103,5134,5139,5143],{"__ignoreMap":28},[33,5063,5064,5066,5068,5070,5072],{"class":35,"line":36},[33,5065,94],{"class":39},[33,5067,583],{"class":97},[33,5069,132],{"class":43},[33,5071,588],{"class":386},[33,5073,390],{"class":43},[33,5075,5076,5078,5080,5082],{"class":35,"line":54},[33,5077,107],{"class":39},[33,5079,598],{"class":113},[33,5081,601],{"class":39},[33,5083,604],{"class":43},[33,5085,5086,5088,5090],{"class":35,"line":61},[33,5087,893],{"class":97},[33,5089,612],{"class":43},[33,5091,5092],{"class":64},"\u002F\u002F urgent — OUTSIDE the transition\n",[33,5094,5095,5097,5099,5101],{"class":35,"line":68},[33,5096,1028],{"class":97},[33,5098,398],{"class":43},[33,5100,401],{"class":39},[33,5102,207],{"class":43},[33,5104,5105,5108,5110,5112,5114,5116,5118,5121,5123,5125,5127,5129,5131],{"class":35,"line":74},[33,5106,5107],{"class":97},"    setFiltered",[33,5109,644],{"class":43},[33,5111,647],{"class":97},[33,5113,132],{"class":43},[33,5115,652],{"class":386},[33,5117,1021],{"class":39},[33,5119,5120],{"class":43}," p.name.",[33,5122,663],{"class":97},[33,5124,666],{"class":43},[33,5126,669],{"class":97},[33,5128,672],{"class":43},[33,5130,663],{"class":97},[33,5132,5133],{"class":43},"())))\n",[33,5135,5136],{"class":35,"line":80},[33,5137,5138],{"class":64},"    \u002F\u002F synchronous filter — stays inside the transition\n",[33,5140,5141],{"class":35,"line":86},[33,5142,419],{"class":43},[33,5144,5145],{"class":35,"line":91},[33,5146,321],{"class":43},[652,5148,5149,5152,5153,5155,5156,5159],{},[5020,5150,5151],{},"The lesson",": only wrap the state updates that are genuinely non-urgent inside ",[30,5154,826],{},", keep urgent updates outside, and keep the transition callback synchronous — ",[30,5157,5158],{},"await"," inside it silently escapes the low-priority scheduling for anything after the awaited expression.",[14,5161,5163],{"id":5162},"key-takeaways","Key Takeaways",[19,5165,5167],{"filename":5166,"language":22},"key_takeaways.js",[24,5168,5170],{"className":26,"code":5169,"language":22,"meta":28,"style":28},"\u002F\u002F 1. React 18's automatic batching groups ALL setState calls into a single\n\u002F\u002F    re-render regardless of where they occur (event handler, promise,\n\u002F\u002F    setTimeout) — a genuine behavior change, not merely an optimization.\n\n\u002F\u002F 2. startTransition marks a state update as LOW-PRIORITY, letting React\n\u002F\u002F    interrupt it in favor of urgent updates. The callback MUST be\n\u002F\u002F    synchronous — only synchronously-scheduled updates are deprioritized.\n\n\u002F\u002F 3. useTransition pairs startTransition with isPending for React-native\n\u002F\u002F    loading state — more reliable than manually tracked boolean flags.\n\n\u002F\u002F 4. useDeferredValue achieves a similar \"lagging\" effect for a value you\n\u002F\u002F    don't own the setter for (a prop, a library hook's return value).\n\n\u002F\u002F 5. Concurrent rendering requires component render functions to be PURE —\n\u002F\u002F    React may call, pause, and discard a render entirely without committing\n\u002F\u002F    it. Side effects during render break under concurrent features.\n\n\u002F\u002F 6. Suspense for data extends the fallback-UI mechanism from code-splitting\n\u002F\u002F    to async data, but requires library\u002Fframework support (React Query's\n\u002F\u002F    useSuspenseQuery, Next.js) — hand-thrown, uncached promises loop.\n\n\u002F\u002F 7. Don't wrap every update in startTransition — it adds overhead for\n\u002F\u002F    cheap renders. Reserve for MEASURABLY expensive renders confirmed\n\u002F\u002F    via profiling. Concurrent helps when expensive renders block urgent\n\u002F\u002F    updates; it doesn't help when the bottleneck is network or the render\n\u002F\u002F    is already fast.\n",[30,5171,5172,5177,5182,5187,5191,5196,5201,5206,5210,5215,5220,5224,5229,5234,5238,5243,5248,5253,5257,5262,5267,5272,5276,5281,5286,5291,5296],{"__ignoreMap":28},[33,5173,5174],{"class":35,"line":36},[33,5175,5176],{"class":64},"\u002F\u002F 1. React 18's automatic batching groups ALL setState calls into a single\n",[33,5178,5179],{"class":35,"line":54},[33,5180,5181],{"class":64},"\u002F\u002F    re-render regardless of where they occur (event handler, promise,\n",[33,5183,5184],{"class":35,"line":61},[33,5185,5186],{"class":64},"\u002F\u002F    setTimeout) — a genuine behavior change, not merely an optimization.\n",[33,5188,5189],{"class":35,"line":68},[33,5190,58],{"emptyLinePlaceholder":57},[33,5192,5193],{"class":35,"line":74},[33,5194,5195],{"class":64},"\u002F\u002F 2. startTransition marks a state update as LOW-PRIORITY, letting React\n",[33,5197,5198],{"class":35,"line":80},[33,5199,5200],{"class":64},"\u002F\u002F    interrupt it in favor of urgent updates. The callback MUST be\n",[33,5202,5203],{"class":35,"line":86},[33,5204,5205],{"class":64},"\u002F\u002F    synchronous — only synchronously-scheduled updates are deprioritized.\n",[33,5207,5208],{"class":35,"line":91},[33,5209,58],{"emptyLinePlaceholder":57},[33,5211,5212],{"class":35,"line":104},[33,5213,5214],{"class":64},"\u002F\u002F 3. useTransition pairs startTransition with isPending for React-native\n",[33,5216,5217],{"class":35,"line":141},[33,5218,5219],{"class":64},"\u002F\u002F    loading state — more reliable than manually tracked boolean flags.\n",[33,5221,5222],{"class":35,"line":169},[33,5223,58],{"emptyLinePlaceholder":57},[33,5225,5226],{"class":35,"line":174},[33,5227,5228],{"class":64},"\u002F\u002F 4. useDeferredValue achieves a similar \"lagging\" effect for a value you\n",[33,5230,5231],{"class":35,"line":188},[33,5232,5233],{"class":64},"\u002F\u002F    don't own the setter for (a prop, a library hook's return value).\n",[33,5235,5236],{"class":35,"line":201},[33,5237,58],{"emptyLinePlaceholder":57},[33,5239,5240],{"class":35,"line":210},[33,5241,5242],{"class":64},"\u002F\u002F 5. Concurrent rendering requires component render functions to be PURE —\n",[33,5244,5245],{"class":35,"line":222},[33,5246,5247],{"class":64},"\u002F\u002F    React may call, pause, and discard a render entirely without committing\n",[33,5249,5250],{"class":35,"line":234},[33,5251,5252],{"class":64},"\u002F\u002F    it. Side effects during render break under concurrent features.\n",[33,5254,5255],{"class":35,"line":240},[33,5256,58],{"emptyLinePlaceholder":57},[33,5258,5259],{"class":35,"line":246},[33,5260,5261],{"class":64},"\u002F\u002F 6. Suspense for data extends the fallback-UI mechanism from code-splitting\n",[33,5263,5264],{"class":35,"line":255},[33,5265,5266],{"class":64},"\u002F\u002F    to async data, but requires library\u002Fframework support (React Query's\n",[33,5268,5269],{"class":35,"line":267},[33,5270,5271],{"class":64},"\u002F\u002F    useSuspenseQuery, Next.js) — hand-thrown, uncached promises loop.\n",[33,5273,5274],{"class":35,"line":273},[33,5275,58],{"emptyLinePlaceholder":57},[33,5277,5278],{"class":35,"line":279},[33,5279,5280],{"class":64},"\u002F\u002F 7. Don't wrap every update in startTransition — it adds overhead for\n",[33,5282,5283],{"class":35,"line":284},[33,5284,5285],{"class":64},"\u002F\u002F    cheap renders. Reserve for MEASURABLY expensive renders confirmed\n",[33,5287,5288],{"class":35,"line":318},[33,5289,5290],{"class":64},"\u002F\u002F    via profiling. Concurrent helps when expensive renders block urgent\n",[33,5292,5293],{"class":35,"line":698},[33,5294,5295],{"class":64},"\u002F\u002F    updates; it doesn't help when the bottleneck is network or the render\n",[33,5297,5298],{"class":35,"line":706},[33,5299,5300],{"class":64},"\u002F\u002F    is already fast.\n",[5302,5303,5304],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}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 .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":28,"searchDepth":54,"depth":54,"links":5306},[5307,5310,5313,5316,5319,5323,5326,5327,5328,5329,5330,5331],{"id":16,"depth":54,"text":17,"children":5308},[5309],{"id":325,"depth":61,"text":326},{"id":447,"depth":54,"text":448,"children":5311},[5312],{"id":807,"depth":61,"text":808},{"id":1045,"depth":54,"text":1046,"children":5314},[5315],{"id":1393,"depth":61,"text":1394},{"id":1781,"depth":54,"text":1782,"children":5317},[5318],{"id":2134,"depth":61,"text":2135},{"id":2488,"depth":54,"text":2489,"children":5320},[5321,5322],{"id":2754,"depth":61,"text":2755},{"id":2960,"depth":61,"text":2961},{"id":3103,"depth":54,"text":3104,"children":5324},[5325],{"id":3312,"depth":61,"text":3313},{"id":3523,"depth":54,"text":3524},{"id":3657,"depth":54,"text":3658},{"id":4458,"depth":54,"text":4459},{"id":4595,"depth":54,"text":4596},{"id":4781,"depth":54,"text":4782},{"id":5162,"depth":54,"text":5163},"React 18 concurrent rendering: useTransition, useDeferredValue, startTransition, automatic batching, Suspense for data fetching, concurrent rendering implications, purity requirements, and when concurrent helps vs doesn't. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F21-concurrent-features",{"title":5,"description":5332},"react\u002F21-concurrent-features","bWK3jLmti62iJ_XyzDqPmM6yAG3iiL1c-sMprET-xVI",1789924652497]