[{"data":1,"prerenderedAt":3094},["ShallowReactive",2],{"page-\u002Freact\u002F05-event-handling":3},{"id":4,"title":5,"body":6,"description":3087,"extension":3088,"meta":3089,"navigation":58,"path":3090,"seo":3091,"stem":3092,"__hash__":3093},"content\u002Freact\u002F05-event-handling.md","05 — Event Handling",{"type":7,"value":8,"toc":3063},"minimark",[9,13,18,349,361,527,531,646,650,741,932,936,1152,1158,1162,1426,1430,1534,1538,1683,1689,1863,2012,2016,2233,2237,2490,2494,2612,2616,2778,2782,2786,2843,2937,2941,3059],[10,11,5],"h1",{"id":12},"_05-event-handling",[14,15,17],"h2",{"id":16},"synthetic-events-what-react-wraps-and-why","Synthetic Events: What React Wraps and Why",[19,20,23],"code-wrapper",{"filename":21,"language":22},"synthetic_event_anatomy.js","javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F React wraps native DOM events in a cross-browser SyntheticEvent.\n\u002F\u002F It pools and normalizes browser differences — you never write\n\u002F\u002F `if (event.srcElement)` or `if (event.target)` cross-browser guards.\n\nfunction SearchForm() {\n  function handleSubmit(event) {\n    \u002F\u002F SyntheticEvent — same API surface as the native Event interface:\n    event.preventDefault()    \u002F\u002F stop the browser's default action (form navigation)\n    event.stopPropagation()   \u002F\u002F stop bubbling to ancestor handlers\n    event.target              \u002F\u002F the actual DOM element that triggered (could be deeply nested)\n    event.currentTarget       \u002F\u002F always the element the handler is attached to\n    event.nativeEvent         \u002F\u002F the raw browser Event object if you need it\n    event.isDefaultPrevented() \u002F\u002F boolean — has preventDefault been called?\n    event.isPropagationStopped()\n\n    const query = new FormData(event.currentTarget).get('query')\n    console.log('Searching for:', query)\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput name=\"query\" type=\"text\" \u002F>\n      \u003Cbutton type=\"submit\">Search\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n","",[30,31,32,41,47,53,60,75,94,100,115,129,138,147,156,170,181,186,220,237,243,248,257,276,304,327,337,343],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sdCPZ","\u002F\u002F React wraps native DOM events in a cross-browser SyntheticEvent.\n",[33,42,44],{"class":35,"line":43},2,[33,45,46],{"class":39},"\u002F\u002F It pools and normalizes browser differences — you never write\n",[33,48,50],{"class":35,"line":49},3,[33,51,52],{"class":39},"\u002F\u002F `if (event.srcElement)` or `if (event.target)` cross-browser guards.\n",[33,54,56],{"class":35,"line":55},4,[33,57,59],{"emptyLinePlaceholder":58},true,"\n",[33,61,63,67,71],{"class":35,"line":62},5,[33,64,66],{"class":65},"svdQ7","function",[33,68,70],{"class":69},"sIsaT"," SearchForm",[33,72,74],{"class":73},"ssxIu","() {\n",[33,76,78,81,84,87,91],{"class":35,"line":77},6,[33,79,80],{"class":65},"  function",[33,82,83],{"class":69}," handleSubmit",[33,85,86],{"class":73},"(",[33,88,90],{"class":89},"sCrzJ","event",[33,92,93],{"class":73},") {\n",[33,95,97],{"class":35,"line":96},7,[33,98,99],{"class":39},"    \u002F\u002F SyntheticEvent — same API surface as the native Event interface:\n",[33,101,103,106,109,112],{"class":35,"line":102},8,[33,104,105],{"class":73},"    event.",[33,107,108],{"class":69},"preventDefault",[33,110,111],{"class":73},"()    ",[33,113,114],{"class":39},"\u002F\u002F stop the browser's default action (form navigation)\n",[33,116,118,120,123,126],{"class":35,"line":117},9,[33,119,105],{"class":73},[33,121,122],{"class":69},"stopPropagation",[33,124,125],{"class":73},"()   ",[33,127,128],{"class":39},"\u002F\u002F stop bubbling to ancestor handlers\n",[33,130,132,135],{"class":35,"line":131},10,[33,133,134],{"class":73},"    event.target              ",[33,136,137],{"class":39},"\u002F\u002F the actual DOM element that triggered (could be deeply nested)\n",[33,139,141,144],{"class":35,"line":140},11,[33,142,143],{"class":73},"    event.currentTarget       ",[33,145,146],{"class":39},"\u002F\u002F always the element the handler is attached to\n",[33,148,150,153],{"class":35,"line":149},12,[33,151,152],{"class":73},"    event.nativeEvent         ",[33,154,155],{"class":39},"\u002F\u002F the raw browser Event object if you need it\n",[33,157,159,161,164,167],{"class":35,"line":158},13,[33,160,105],{"class":73},[33,162,163],{"class":69},"isDefaultPrevented",[33,165,166],{"class":73},"() ",[33,168,169],{"class":39},"\u002F\u002F boolean — has preventDefault been called?\n",[33,171,173,175,178],{"class":35,"line":172},14,[33,174,105],{"class":73},[33,176,177],{"class":69},"isPropagationStopped",[33,179,180],{"class":73},"()\n",[33,182,184],{"class":35,"line":183},15,[33,185,59],{"emptyLinePlaceholder":58},[33,187,189,192,196,199,202,205,208,211,213,217],{"class":35,"line":188},16,[33,190,191],{"class":65},"    const",[33,193,195],{"class":194},"snvgF"," query",[33,197,198],{"class":65}," =",[33,200,201],{"class":65}," new",[33,203,204],{"class":69}," FormData",[33,206,207],{"class":73},"(event.currentTarget).",[33,209,210],{"class":69},"get",[33,212,86],{"class":73},[33,214,216],{"class":215},"sJ6F3","'query'",[33,218,219],{"class":73},")\n",[33,221,223,226,229,231,234],{"class":35,"line":222},17,[33,224,225],{"class":73},"    console.",[33,227,228],{"class":69},"log",[33,230,86],{"class":73},[33,232,233],{"class":215},"'Searching for:'",[33,235,236],{"class":73},", query)\n",[33,238,240],{"class":35,"line":239},18,[33,241,242],{"class":73},"  }\n",[33,244,246],{"class":35,"line":245},19,[33,247,59],{"emptyLinePlaceholder":58},[33,249,251,254],{"class":35,"line":250},20,[33,252,253],{"class":65},"  return",[33,255,256],{"class":73}," (\n",[33,258,260,263,267,270,273],{"class":35,"line":259},21,[33,261,262],{"class":73},"    \u003C",[33,264,266],{"class":265},"sk71V","form",[33,268,269],{"class":69}," onSubmit",[33,271,272],{"class":65},"=",[33,274,275],{"class":73},"{handleSubmit}>\n",[33,277,279,282,285,288,290,293,296,298,301],{"class":35,"line":278},22,[33,280,281],{"class":73},"      \u003C",[33,283,284],{"class":265},"input",[33,286,287],{"class":69}," name",[33,289,272],{"class":65},[33,291,292],{"class":215},"\"query\"",[33,294,295],{"class":69}," type",[33,297,272],{"class":65},[33,299,300],{"class":215},"\"text\"",[33,302,303],{"class":73}," \u002F>\n",[33,305,307,309,312,314,316,319,322,324],{"class":35,"line":306},23,[33,308,281],{"class":73},[33,310,311],{"class":265},"button",[33,313,295],{"class":69},[33,315,272],{"class":65},[33,317,318],{"class":215},"\"submit\"",[33,320,321],{"class":73},">Search\u003C\u002F",[33,323,311],{"class":265},[33,325,326],{"class":73},">\n",[33,328,330,333,335],{"class":35,"line":329},24,[33,331,332],{"class":73},"    \u003C\u002F",[33,334,266],{"class":265},[33,336,326],{"class":73},[33,338,340],{"class":35,"line":339},25,[33,341,342],{"class":73},"  )\n",[33,344,346],{"class":35,"line":345},26,[33,347,348],{"class":73},"}\n",[350,351,353,356,357,360],"h3",{"id":352},"target-vs-currenttarget-the-delegates-trap",[30,354,355],{},"target"," vs. ",[30,358,359],{},"currentTarget"," — The Delegate's Trap",[19,362,364],{"filename":363,"language":22},"target_vs_currenttarget.js",[24,365,367],{"className":26,"code":366,"language":22,"meta":28,"style":28},"\u002F\u002F event.target       = the element the user ACTUALLY interacted with (could be a child)\n\u002F\u002F event.currentTarget = the element the HANDLER is attached to (always the same element)\n\nfunction IconButton({ onActivate }) {\n  return (\n    \u003Cbutton onClick={(e) => {\n      \u002F\u002F If the user clicks the \u003Csvg> icon or the \u003Cspan> text:\n      console.log(e.target)        \u002F\u002F → \u003Csvg> or \u003Cspan> (the actual click target)\n      console.log(e.currentTarget) \u002F\u002F → \u003Cbutton> (where onClick is registered)\n      onActivate()\n    }}>\n      \u003Csvg \u002F>\u003Cspan>Delete\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  )\n}\n\n\u002F\u002F PRODUCTION PATTERN: use currentTarget when you need the handler's element,\n\u002F\u002F target when you need to know what was actually clicked (event delegation).\n\u002F\u002F Mixing them up is the #1 source of \"wrong element\" bugs in icon-heavy UIs.\n",[30,368,369,374,379,383,399,405,431,436,449,461,468,473,492,500,504,508,512,517,522],{"__ignoreMap":28},[33,370,371],{"class":35,"line":36},[33,372,373],{"class":39},"\u002F\u002F event.target       = the element the user ACTUALLY interacted with (could be a child)\n",[33,375,376],{"class":35,"line":43},[33,377,378],{"class":39},"\u002F\u002F event.currentTarget = the element the HANDLER is attached to (always the same element)\n",[33,380,381],{"class":35,"line":49},[33,382,59],{"emptyLinePlaceholder":58},[33,384,385,387,390,393,396],{"class":35,"line":55},[33,386,66],{"class":65},[33,388,389],{"class":69}," IconButton",[33,391,392],{"class":73},"({ ",[33,394,395],{"class":89},"onActivate",[33,397,398],{"class":73}," }) {\n",[33,400,401,403],{"class":35,"line":62},[33,402,253],{"class":65},[33,404,256],{"class":73},[33,406,407,409,411,414,416,419,422,425,428],{"class":35,"line":77},[33,408,262],{"class":73},[33,410,311],{"class":265},[33,412,413],{"class":69}," onClick",[33,415,272],{"class":65},[33,417,418],{"class":73},"{(",[33,420,421],{"class":89},"e",[33,423,424],{"class":73},") ",[33,426,427],{"class":65},"=>",[33,429,430],{"class":73}," {\n",[33,432,433],{"class":35,"line":96},[33,434,435],{"class":39},"      \u002F\u002F If the user clicks the \u003Csvg> icon or the \u003Cspan> text:\n",[33,437,438,441,443,446],{"class":35,"line":102},[33,439,440],{"class":73},"      console.",[33,442,228],{"class":69},[33,444,445],{"class":73},"(e.target)        ",[33,447,448],{"class":39},"\u002F\u002F → \u003Csvg> or \u003Cspan> (the actual click target)\n",[33,450,451,453,455,458],{"class":35,"line":117},[33,452,440],{"class":73},[33,454,228],{"class":69},[33,456,457],{"class":73},"(e.currentTarget) ",[33,459,460],{"class":39},"\u002F\u002F → \u003Cbutton> (where onClick is registered)\n",[33,462,463,466],{"class":35,"line":131},[33,464,465],{"class":69},"      onActivate",[33,467,180],{"class":73},[33,469,470],{"class":35,"line":140},[33,471,472],{"class":73},"    }}>\n",[33,474,475,477,480,483,485,488,490],{"class":35,"line":149},[33,476,281],{"class":73},[33,478,479],{"class":265},"svg",[33,481,482],{"class":73}," \u002F>\u003C",[33,484,33],{"class":265},[33,486,487],{"class":73},">Delete\u003C\u002F",[33,489,33],{"class":265},[33,491,326],{"class":73},[33,493,494,496,498],{"class":35,"line":158},[33,495,332],{"class":73},[33,497,311],{"class":265},[33,499,326],{"class":73},[33,501,502],{"class":35,"line":172},[33,503,342],{"class":73},[33,505,506],{"class":35,"line":183},[33,507,348],{"class":73},[33,509,510],{"class":35,"line":188},[33,511,59],{"emptyLinePlaceholder":58},[33,513,514],{"class":35,"line":222},[33,515,516],{"class":39},"\u002F\u002F PRODUCTION PATTERN: use currentTarget when you need the handler's element,\n",[33,518,519],{"class":35,"line":239},[33,520,521],{"class":39},"\u002F\u002F target when you need to know what was actually clicked (event delegation).\n",[33,523,524],{"class":35,"line":245},[33,525,526],{"class":39},"\u002F\u002F Mixing them up is the #1 source of \"wrong element\" bugs in icon-heavy UIs.\n",[14,528,530],{"id":529},"react-17-event-delegation-architecture","React 17+ Event Delegation Architecture",[19,532,534],{"filename":533,"language":22},"event_delegation_architecture.js",[24,535,537],{"className":26,"code":536,"language":22,"meta":28,"style":28},"\u002F\u002F ── REACT ≤16: single listener on document ────────────────────────────\n\u002F\u002F React attached ONE listener per event type to `document` and dispatched\n\u002F\u002F synthetic events from there. Fast (one listener vs thousands), but broke\n\u002F\u002F isolation when multiple React trees or non-React widgets shared a page.\n\n\u002F\u002F ── REACT 17+: listener on the root container ─────────────────────────\n\u002F\u002F React attaches listeners to the root DOM container (the element passed\n\u002F\u002F to createRoot). Each React tree is isolated — no cross-tree event stealing.\n\n\u002F\u002F You NEVER write this manually — React does it internally:\n\u002F\u002F createRoot(document.getElementById('root')).render(\u003CApp \u002F>)\n\u002F\u002F   → rootContainerElement.addEventListener('click', dispatchToSyntheticSystem)\n\u002F\u002F   → rootContainerElement.addEventListener('change', dispatchToSyntheticSystem)\n\u002F\u002F   → ...one per supported event type, attached ONCE at the root.\n\n\u002F\u002F WHY THIS MATTERS:\n\u002F\u002F 1. Multiple React versions on one page (microfrontends) — each tree's\n\u002F\u002F    events are scoped to its own root, not the global document.\n\u002F\u002F 2. Non-React widgets (legacy jQuery, vanilla JS) on the same page —\n\u002F\u002F    React no longer intercepts their events at the document level.\n\u002F\u002F 3. Event pooling was removed in React 17 — SyntheticEvent objects are\n\u002F\u002F    no longer recycled, so accessing them asynchronously is safe.\n",[30,538,539,544,549,554,559,563,568,573,578,582,587,592,597,602,607,611,616,621,626,631,636,641],{"__ignoreMap":28},[33,540,541],{"class":35,"line":36},[33,542,543],{"class":39},"\u002F\u002F ── REACT ≤16: single listener on document ────────────────────────────\n",[33,545,546],{"class":35,"line":43},[33,547,548],{"class":39},"\u002F\u002F React attached ONE listener per event type to `document` and dispatched\n",[33,550,551],{"class":35,"line":49},[33,552,553],{"class":39},"\u002F\u002F synthetic events from there. Fast (one listener vs thousands), but broke\n",[33,555,556],{"class":35,"line":55},[33,557,558],{"class":39},"\u002F\u002F isolation when multiple React trees or non-React widgets shared a page.\n",[33,560,561],{"class":35,"line":62},[33,562,59],{"emptyLinePlaceholder":58},[33,564,565],{"class":35,"line":77},[33,566,567],{"class":39},"\u002F\u002F ── REACT 17+: listener on the root container ─────────────────────────\n",[33,569,570],{"class":35,"line":96},[33,571,572],{"class":39},"\u002F\u002F React attaches listeners to the root DOM container (the element passed\n",[33,574,575],{"class":35,"line":102},[33,576,577],{"class":39},"\u002F\u002F to createRoot). Each React tree is isolated — no cross-tree event stealing.\n",[33,579,580],{"class":35,"line":117},[33,581,59],{"emptyLinePlaceholder":58},[33,583,584],{"class":35,"line":131},[33,585,586],{"class":39},"\u002F\u002F You NEVER write this manually — React does it internally:\n",[33,588,589],{"class":35,"line":140},[33,590,591],{"class":39},"\u002F\u002F createRoot(document.getElementById('root')).render(\u003CApp \u002F>)\n",[33,593,594],{"class":35,"line":149},[33,595,596],{"class":39},"\u002F\u002F   → rootContainerElement.addEventListener('click', dispatchToSyntheticSystem)\n",[33,598,599],{"class":35,"line":158},[33,600,601],{"class":39},"\u002F\u002F   → rootContainerElement.addEventListener('change', dispatchToSyntheticSystem)\n",[33,603,604],{"class":35,"line":172},[33,605,606],{"class":39},"\u002F\u002F   → ...one per supported event type, attached ONCE at the root.\n",[33,608,609],{"class":35,"line":183},[33,610,59],{"emptyLinePlaceholder":58},[33,612,613],{"class":35,"line":188},[33,614,615],{"class":39},"\u002F\u002F WHY THIS MATTERS:\n",[33,617,618],{"class":35,"line":222},[33,619,620],{"class":39},"\u002F\u002F 1. Multiple React versions on one page (microfrontends) — each tree's\n",[33,622,623],{"class":35,"line":239},[33,624,625],{"class":39},"\u002F\u002F    events are scoped to its own root, not the global document.\n",[33,627,628],{"class":35,"line":245},[33,629,630],{"class":39},"\u002F\u002F 2. Non-React widgets (legacy jQuery, vanilla JS) on the same page —\n",[33,632,633],{"class":35,"line":250},[33,634,635],{"class":39},"\u002F\u002F    React no longer intercepts their events at the document level.\n",[33,637,638],{"class":35,"line":259},[33,639,640],{"class":39},"\u002F\u002F 3. Event pooling was removed in React 17 — SyntheticEvent objects are\n",[33,642,643],{"class":35,"line":278},[33,644,645],{"class":39},"\u002F\u002F    no longer recycled, so accessing them asynchronously is safe.\n",[14,647,649],{"id":648},"handler-binding-reference-vs-invocation","Handler Binding: Reference vs. Invocation",[19,651,653],{"filename":652,"language":22},"handler_binding_anti_pattern.js",[24,654,656],{"className":26,"code":655,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: calling the function during render instead of passing a reference\n\u002F\u002F onClick={doSomething()} invokes doSomething IMMEDIATELY during render.\n\u002F\u002F Whatever it returns (probably undefined) becomes the onClick handler.\n\u002F\u002F The button is now inert — no error, no warning, just silence.\n\nfunction BadButton({ onDelete, item }) {\n  return \u003Cbutton onClick={onDelete(item.id)}>Delete\u003C\u002Fbutton>\n  \u002F\u002F onDelete(item.id) runs during EVERY render, not on click.\n  \u002F\u002F If onDelete mutates state → infinite loop or immediate deletion.\n}\n",[30,657,658,663,668,673,678,682,702,727,732,737],{"__ignoreMap":28},[33,659,660],{"class":35,"line":36},[33,661,662],{"class":39},"\u002F\u002F ANTI-PATTERN: calling the function during render instead of passing a reference\n",[33,664,665],{"class":35,"line":43},[33,666,667],{"class":39},"\u002F\u002F onClick={doSomething()} invokes doSomething IMMEDIATELY during render.\n",[33,669,670],{"class":35,"line":49},[33,671,672],{"class":39},"\u002F\u002F Whatever it returns (probably undefined) becomes the onClick handler.\n",[33,674,675],{"class":35,"line":55},[33,676,677],{"class":39},"\u002F\u002F The button is now inert — no error, no warning, just silence.\n",[33,679,680],{"class":35,"line":62},[33,681,59],{"emptyLinePlaceholder":58},[33,683,684,686,689,691,694,697,700],{"class":35,"line":77},[33,685,66],{"class":65},[33,687,688],{"class":69}," BadButton",[33,690,392],{"class":73},[33,692,693],{"class":89},"onDelete",[33,695,696],{"class":73},", ",[33,698,699],{"class":89},"item",[33,701,398],{"class":73},[33,703,704,706,709,711,713,715,718,720,723,725],{"class":35,"line":96},[33,705,253],{"class":65},[33,707,708],{"class":73}," \u003C",[33,710,311],{"class":265},[33,712,413],{"class":69},[33,714,272],{"class":65},[33,716,717],{"class":73},"{",[33,719,693],{"class":69},[33,721,722],{"class":73},"(item.id)}>Delete\u003C\u002F",[33,724,311],{"class":265},[33,726,326],{"class":73},[33,728,729],{"class":35,"line":102},[33,730,731],{"class":39},"  \u002F\u002F onDelete(item.id) runs during EVERY render, not on click.\n",[33,733,734],{"class":35,"line":117},[33,735,736],{"class":39},"  \u002F\u002F If onDelete mutates state → infinite loop or immediate deletion.\n",[33,738,739],{"class":35,"line":131},[33,740,348],{"class":73},[19,742,744],{"filename":743,"language":22},"handler_binding_production.js",[24,745,747],{"className":26,"code":746,"language":22,"meta":28,"style":28},"\u002F\u002F PRODUCTION: pass a reference, not an invocation\n\n\u002F\u002F 1. No arguments → pass the function reference directly (no parens)\n\u003Cbutton onClick={doSomething}>Click\u003C\u002Fbutton>\n\n\u002F\u002F 2. Arguments needed → wrap in an arrow function (creates a new fn per render)\n\u003Cbutton onClick={() => deleteItem(item.id)}>Delete\u003C\u002Fbutton>\n\n\u002F\u002F 3. Handler factory → returns a stable function reference\nfunction makeDeleteHandler(id) {\n  return () => deleteItem(id)\n}\n\u003Cbutton onClick={makeDeleteHandler(item.id)}>Delete\u003C\u002Fbutton>\n\n\u002F\u002F 4. bind() — also creates a new function per render, same as arrow fn\n\u003Cbutton onClick={deleteItem.bind(null, item.id)}>Delete\u003C\u002Fbutton>\n\n\u002F\u002F APPROACHES 2 AND 4 create a new function on every render. For most components\n\u002F\u002F this is fine. It only matters when the child is React.memo'd AND the handler\n\u002F\u002F is in the dependency array of a useEffect\u002FuseCallback. See Chapter 9.\n",[30,748,749,754,758,763,781,785,790,814,818,823,837,851,855,876,880,885,913,917,922,927],{"__ignoreMap":28},[33,750,751],{"class":35,"line":36},[33,752,753],{"class":39},"\u002F\u002F PRODUCTION: pass a reference, not an invocation\n",[33,755,756],{"class":35,"line":43},[33,757,59],{"emptyLinePlaceholder":58},[33,759,760],{"class":35,"line":49},[33,761,762],{"class":39},"\u002F\u002F 1. No arguments → pass the function reference directly (no parens)\n",[33,764,765,768,770,772,774,777,779],{"class":35,"line":55},[33,766,767],{"class":73},"\u003C",[33,769,311],{"class":265},[33,771,413],{"class":69},[33,773,272],{"class":65},[33,775,776],{"class":73},"{doSomething}>Click\u003C\u002F",[33,778,311],{"class":265},[33,780,326],{"class":73},[33,782,783],{"class":35,"line":62},[33,784,59],{"emptyLinePlaceholder":58},[33,786,787],{"class":35,"line":77},[33,788,789],{"class":39},"\u002F\u002F 2. Arguments needed → wrap in an arrow function (creates a new fn per render)\n",[33,791,792,794,796,798,800,803,805,808,810,812],{"class":35,"line":96},[33,793,767],{"class":73},[33,795,311],{"class":265},[33,797,413],{"class":69},[33,799,272],{"class":65},[33,801,802],{"class":73},"{() ",[33,804,427],{"class":65},[33,806,807],{"class":69}," deleteItem",[33,809,722],{"class":73},[33,811,311],{"class":265},[33,813,326],{"class":73},[33,815,816],{"class":35,"line":102},[33,817,59],{"emptyLinePlaceholder":58},[33,819,820],{"class":35,"line":117},[33,821,822],{"class":39},"\u002F\u002F 3. Handler factory → returns a stable function reference\n",[33,824,825,827,830,832,835],{"class":35,"line":131},[33,826,66],{"class":65},[33,828,829],{"class":69}," makeDeleteHandler",[33,831,86],{"class":73},[33,833,834],{"class":89},"id",[33,836,93],{"class":73},[33,838,839,841,844,846,848],{"class":35,"line":140},[33,840,253],{"class":65},[33,842,843],{"class":73}," () ",[33,845,427],{"class":65},[33,847,807],{"class":69},[33,849,850],{"class":73},"(id)\n",[33,852,853],{"class":35,"line":149},[33,854,348],{"class":73},[33,856,857,859,861,863,865,867,870,872,874],{"class":35,"line":158},[33,858,767],{"class":73},[33,860,311],{"class":265},[33,862,413],{"class":69},[33,864,272],{"class":65},[33,866,717],{"class":73},[33,868,869],{"class":69},"makeDeleteHandler",[33,871,722],{"class":73},[33,873,311],{"class":265},[33,875,326],{"class":73},[33,877,878],{"class":35,"line":172},[33,879,59],{"emptyLinePlaceholder":58},[33,881,882],{"class":35,"line":183},[33,883,884],{"class":39},"\u002F\u002F 4. bind() — also creates a new function per render, same as arrow fn\n",[33,886,887,889,891,893,895,898,901,903,906,909,911],{"class":35,"line":188},[33,888,767],{"class":73},[33,890,311],{"class":265},[33,892,413],{"class":69},[33,894,272],{"class":65},[33,896,897],{"class":73},"{deleteItem.",[33,899,900],{"class":69},"bind",[33,902,86],{"class":73},[33,904,905],{"class":194},"null",[33,907,908],{"class":73},", item.id)}>Delete\u003C\u002F",[33,910,311],{"class":265},[33,912,326],{"class":73},[33,914,915],{"class":35,"line":222},[33,916,59],{"emptyLinePlaceholder":58},[33,918,919],{"class":35,"line":239},[33,920,921],{"class":39},"\u002F\u002F APPROACHES 2 AND 4 create a new function on every render. For most components\n",[33,923,924],{"class":35,"line":245},[33,925,926],{"class":39},"\u002F\u002F this is fine. It only matters when the child is React.memo'd AND the handler\n",[33,928,929],{"class":35,"line":250},[33,930,931],{"class":39},"\u002F\u002F is in the dependency array of a useEffect\u002FuseCallback. See Chapter 9.\n",[350,933,935],{"id":934},"production-pattern-list-item-handlers","Production Pattern: List Item Handlers",[19,937,939],{"filename":938,"language":22},"list_item_handlers.js",[24,940,942],{"className":26,"code":941,"language":22,"meta":28,"style":28},"function TodoList({ todos, onToggle, onDelete }) {\n  return (\n    \u003Cul>\n      {todos.map(todo => (\n        \u003Cli key={todo.id}>\n          \u003Cinput\n            type=\"checkbox\"\n            checked={todo.done}\n            onChange={() => onToggle(todo.id)}\n          \u002F>\n          \u003Cspan>{todo.text}\u003C\u002Fspan>\n          \u003Cbutton onClick={() => onDelete(todo.id)}>Remove\u003C\u002Fbutton>\n        \u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  )\n}\n\n\u002F\u002F Every row creates two inline arrow functions per render. Fine for most apps.\n\u002F\u002F Premature optimization here is a common mistake — reach for useCallback (Ch 9)\n\u002F\u002F ONLY when you've measured a re-render problem with React DevTools Profiler.\n",[30,943,944,967,973,982,1000,1016,1024,1034,1044,1061,1066,1079,1103,1112,1117,1125,1129,1133,1137,1142,1147],{"__ignoreMap":28},[33,945,946,948,951,953,956,958,961,963,965],{"class":35,"line":36},[33,947,66],{"class":65},[33,949,950],{"class":69}," TodoList",[33,952,392],{"class":73},[33,954,955],{"class":89},"todos",[33,957,696],{"class":73},[33,959,960],{"class":89},"onToggle",[33,962,696],{"class":73},[33,964,693],{"class":89},[33,966,398],{"class":73},[33,968,969,971],{"class":35,"line":43},[33,970,253],{"class":65},[33,972,256],{"class":73},[33,974,975,977,980],{"class":35,"line":49},[33,976,262],{"class":73},[33,978,979],{"class":265},"ul",[33,981,326],{"class":73},[33,983,984,987,990,992,995,998],{"class":35,"line":55},[33,985,986],{"class":73},"      {todos.",[33,988,989],{"class":69},"map",[33,991,86],{"class":73},[33,993,994],{"class":89},"todo",[33,996,997],{"class":65}," =>",[33,999,256],{"class":73},[33,1001,1002,1005,1008,1011,1013],{"class":35,"line":62},[33,1003,1004],{"class":73},"        \u003C",[33,1006,1007],{"class":265},"li",[33,1009,1010],{"class":69}," key",[33,1012,272],{"class":65},[33,1014,1015],{"class":73},"{todo.id}>\n",[33,1017,1018,1021],{"class":35,"line":77},[33,1019,1020],{"class":73},"          \u003C",[33,1022,1023],{"class":265},"input\n",[33,1025,1026,1029,1031],{"class":35,"line":96},[33,1027,1028],{"class":69},"            type",[33,1030,272],{"class":65},[33,1032,1033],{"class":215},"\"checkbox\"\n",[33,1035,1036,1039,1041],{"class":35,"line":102},[33,1037,1038],{"class":69},"            checked",[33,1040,272],{"class":65},[33,1042,1043],{"class":73},"{todo.done}\n",[33,1045,1046,1049,1051,1053,1055,1058],{"class":35,"line":117},[33,1047,1048],{"class":69},"            onChange",[33,1050,272],{"class":65},[33,1052,802],{"class":73},[33,1054,427],{"class":65},[33,1056,1057],{"class":69}," onToggle",[33,1059,1060],{"class":73},"(todo.id)}\n",[33,1062,1063],{"class":35,"line":131},[33,1064,1065],{"class":73},"          \u002F>\n",[33,1067,1068,1070,1072,1075,1077],{"class":35,"line":140},[33,1069,1020],{"class":73},[33,1071,33],{"class":265},[33,1073,1074],{"class":73},">{todo.text}\u003C\u002F",[33,1076,33],{"class":265},[33,1078,326],{"class":73},[33,1080,1081,1083,1085,1087,1089,1091,1093,1096,1099,1101],{"class":35,"line":149},[33,1082,1020],{"class":73},[33,1084,311],{"class":265},[33,1086,413],{"class":69},[33,1088,272],{"class":65},[33,1090,802],{"class":73},[33,1092,427],{"class":65},[33,1094,1095],{"class":69}," onDelete",[33,1097,1098],{"class":73},"(todo.id)}>Remove\u003C\u002F",[33,1100,311],{"class":265},[33,1102,326],{"class":73},[33,1104,1105,1108,1110],{"class":35,"line":158},[33,1106,1107],{"class":73},"        \u003C\u002F",[33,1109,1007],{"class":265},[33,1111,326],{"class":73},[33,1113,1114],{"class":35,"line":172},[33,1115,1116],{"class":73},"      ))}\n",[33,1118,1119,1121,1123],{"class":35,"line":183},[33,1120,332],{"class":73},[33,1122,979],{"class":265},[33,1124,326],{"class":73},[33,1126,1127],{"class":35,"line":188},[33,1128,342],{"class":73},[33,1130,1131],{"class":35,"line":222},[33,1132,348],{"class":73},[33,1134,1135],{"class":35,"line":239},[33,1136,59],{"emptyLinePlaceholder":58},[33,1138,1139],{"class":35,"line":245},[33,1140,1141],{"class":39},"\u002F\u002F Every row creates two inline arrow functions per render. Fine for most apps.\n",[33,1143,1144],{"class":35,"line":250},[33,1145,1146],{"class":39},"\u002F\u002F Premature optimization here is a common mistake — reach for useCallback (Ch 9)\n",[33,1148,1149],{"class":35,"line":259},[33,1150,1151],{"class":39},"\u002F\u002F ONLY when you've measured a re-render problem with React DevTools Profiler.\n",[14,1153,1155,1157],{"id":1154},"preventdefault-patterns",[30,1156,108],{}," Patterns",[350,1159,1161],{"id":1160},"forms-the-1-react-beginner-bug","Forms — The #1 React Beginner Bug",[19,1163,1165],{"filename":1164,"language":22},"prevent_default_form.js",[24,1166,1168],{"className":26,"code":1167,"language":22,"meta":28,"style":28},"function LoginForm({ onSubmit }) {\n  const [email, setEmail] = useState('')\n  const [password, setPassword] = useState('')\n\n  function handleSubmit(event) {\n    event.preventDefault()  \u002F\u002F CRITICAL: stops the browser's full-page navigation\u002Freload\n    \u002F\u002F Without this, the browser navigates → all React state is wiped instantly.\n    \u002F\u002F The bug looks like \"my app randomly resets\" — not obviously form-related.\n    onSubmit({ email, password })\n  }\n\n  return (\n    \u003Cform onSubmit={handleSubmit}>\n      \u003Cinput value={email} onChange={e => setEmail(e.target.value)} type=\"email\" \u002F>\n      \u003Cinput value={password} onChange={e => setPassword(e.target.value)} type=\"password\" \u002F>\n      \u003Cbutton type=\"submit\">Log In\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  )\n}\n",[30,1169,1170,1184,1215,1241,1245,1257,1269,1274,1279,1287,1291,1295,1301,1313,1354,1391,1410,1418,1422],{"__ignoreMap":28},[33,1171,1172,1174,1177,1179,1182],{"class":35,"line":36},[33,1173,66],{"class":65},[33,1175,1176],{"class":69}," LoginForm",[33,1178,392],{"class":73},[33,1180,1181],{"class":89},"onSubmit",[33,1183,398],{"class":73},[33,1185,1186,1189,1192,1195,1197,1200,1203,1205,1208,1210,1213],{"class":35,"line":43},[33,1187,1188],{"class":65},"  const",[33,1190,1191],{"class":73}," [",[33,1193,1194],{"class":194},"email",[33,1196,696],{"class":73},[33,1198,1199],{"class":194},"setEmail",[33,1201,1202],{"class":73},"] ",[33,1204,272],{"class":65},[33,1206,1207],{"class":69}," useState",[33,1209,86],{"class":73},[33,1211,1212],{"class":215},"''",[33,1214,219],{"class":73},[33,1216,1217,1219,1221,1224,1226,1229,1231,1233,1235,1237,1239],{"class":35,"line":49},[33,1218,1188],{"class":65},[33,1220,1191],{"class":73},[33,1222,1223],{"class":194},"password",[33,1225,696],{"class":73},[33,1227,1228],{"class":194},"setPassword",[33,1230,1202],{"class":73},[33,1232,272],{"class":65},[33,1234,1207],{"class":69},[33,1236,86],{"class":73},[33,1238,1212],{"class":215},[33,1240,219],{"class":73},[33,1242,1243],{"class":35,"line":55},[33,1244,59],{"emptyLinePlaceholder":58},[33,1246,1247,1249,1251,1253,1255],{"class":35,"line":62},[33,1248,80],{"class":65},[33,1250,83],{"class":69},[33,1252,86],{"class":73},[33,1254,90],{"class":89},[33,1256,93],{"class":73},[33,1258,1259,1261,1263,1266],{"class":35,"line":77},[33,1260,105],{"class":73},[33,1262,108],{"class":69},[33,1264,1265],{"class":73},"()  ",[33,1267,1268],{"class":39},"\u002F\u002F CRITICAL: stops the browser's full-page navigation\u002Freload\n",[33,1270,1271],{"class":35,"line":96},[33,1272,1273],{"class":39},"    \u002F\u002F Without this, the browser navigates → all React state is wiped instantly.\n",[33,1275,1276],{"class":35,"line":102},[33,1277,1278],{"class":39},"    \u002F\u002F The bug looks like \"my app randomly resets\" — not obviously form-related.\n",[33,1280,1281,1284],{"class":35,"line":117},[33,1282,1283],{"class":69},"    onSubmit",[33,1285,1286],{"class":73},"({ email, password })\n",[33,1288,1289],{"class":35,"line":131},[33,1290,242],{"class":73},[33,1292,1293],{"class":35,"line":140},[33,1294,59],{"emptyLinePlaceholder":58},[33,1296,1297,1299],{"class":35,"line":149},[33,1298,253],{"class":65},[33,1300,256],{"class":73},[33,1302,1303,1305,1307,1309,1311],{"class":35,"line":158},[33,1304,262],{"class":73},[33,1306,266],{"class":265},[33,1308,269],{"class":69},[33,1310,272],{"class":65},[33,1312,275],{"class":73},[33,1314,1315,1317,1319,1322,1324,1327,1330,1332,1334,1336,1338,1341,1344,1347,1349,1352],{"class":35,"line":172},[33,1316,281],{"class":73},[33,1318,284],{"class":265},[33,1320,1321],{"class":69}," value",[33,1323,272],{"class":65},[33,1325,1326],{"class":73},"{email} ",[33,1328,1329],{"class":69},"onChange",[33,1331,272],{"class":65},[33,1333,717],{"class":73},[33,1335,421],{"class":89},[33,1337,997],{"class":65},[33,1339,1340],{"class":69}," setEmail",[33,1342,1343],{"class":73},"(e.target.value)} ",[33,1345,1346],{"class":69},"type",[33,1348,272],{"class":65},[33,1350,1351],{"class":215},"\"email\"",[33,1353,303],{"class":73},[33,1355,1356,1358,1360,1362,1364,1367,1369,1371,1373,1375,1377,1380,1382,1384,1386,1389],{"class":35,"line":183},[33,1357,281],{"class":73},[33,1359,284],{"class":265},[33,1361,1321],{"class":69},[33,1363,272],{"class":65},[33,1365,1366],{"class":73},"{password} ",[33,1368,1329],{"class":69},[33,1370,272],{"class":65},[33,1372,717],{"class":73},[33,1374,421],{"class":89},[33,1376,997],{"class":65},[33,1378,1379],{"class":69}," setPassword",[33,1381,1343],{"class":73},[33,1383,1346],{"class":69},[33,1385,272],{"class":65},[33,1387,1388],{"class":215},"\"password\"",[33,1390,303],{"class":73},[33,1392,1393,1395,1397,1399,1401,1403,1406,1408],{"class":35,"line":188},[33,1394,281],{"class":73},[33,1396,311],{"class":265},[33,1398,295],{"class":69},[33,1400,272],{"class":65},[33,1402,318],{"class":215},[33,1404,1405],{"class":73},">Log In\u003C\u002F",[33,1407,311],{"class":265},[33,1409,326],{"class":73},[33,1411,1412,1414,1416],{"class":35,"line":222},[33,1413,332],{"class":73},[33,1415,266],{"class":265},[33,1417,326],{"class":73},[33,1419,1420],{"class":35,"line":239},[33,1421,342],{"class":73},[33,1423,1424],{"class":35,"line":245},[33,1425,348],{"class":73},[350,1427,1429],{"id":1428},"client-side-routing-intercepting-links","Client-Side Routing — Intercepting Links",[19,1431,1433],{"filename":1432,"language":22},"prevent_default_link.js",[24,1434,1436],{"className":26,"code":1435,"language":22,"meta":28,"style":28},"function CustomLink({ href, onNavigate, children }) {\n  function handleClick(event) {\n    event.preventDefault()  \u002F\u002F stop the browser's real navigation (full reload)\n    onNavigate(href)         \u002F\u002F let a client-side router handle it (Chapter 19)\n  }\n  return \u003Ca href={href} onClick={handleClick}>{children}\u003C\u002Fa>\n}\n",[30,1437,1438,1462,1475,1486,1497,1501,1530],{"__ignoreMap":28},[33,1439,1440,1442,1445,1447,1450,1452,1455,1457,1460],{"class":35,"line":36},[33,1441,66],{"class":65},[33,1443,1444],{"class":69}," CustomLink",[33,1446,392],{"class":73},[33,1448,1449],{"class":89},"href",[33,1451,696],{"class":73},[33,1453,1454],{"class":89},"onNavigate",[33,1456,696],{"class":73},[33,1458,1459],{"class":89},"children",[33,1461,398],{"class":73},[33,1463,1464,1466,1469,1471,1473],{"class":35,"line":43},[33,1465,80],{"class":65},[33,1467,1468],{"class":69}," handleClick",[33,1470,86],{"class":73},[33,1472,90],{"class":89},[33,1474,93],{"class":73},[33,1476,1477,1479,1481,1483],{"class":35,"line":49},[33,1478,105],{"class":73},[33,1480,108],{"class":69},[33,1482,1265],{"class":73},[33,1484,1485],{"class":39},"\u002F\u002F stop the browser's real navigation (full reload)\n",[33,1487,1488,1491,1494],{"class":35,"line":55},[33,1489,1490],{"class":69},"    onNavigate",[33,1492,1493],{"class":73},"(href)         ",[33,1495,1496],{"class":39},"\u002F\u002F let a client-side router handle it (Chapter 19)\n",[33,1498,1499],{"class":35,"line":62},[33,1500,242],{"class":73},[33,1502,1503,1505,1507,1510,1513,1515,1518,1521,1523,1526,1528],{"class":35,"line":77},[33,1504,253],{"class":65},[33,1506,708],{"class":73},[33,1508,1509],{"class":265},"a",[33,1511,1512],{"class":69}," href",[33,1514,272],{"class":65},[33,1516,1517],{"class":73},"{href} ",[33,1519,1520],{"class":69},"onClick",[33,1522,272],{"class":65},[33,1524,1525],{"class":73},"{handleClick}>{children}\u003C\u002F",[33,1527,1509],{"class":265},[33,1529,326],{"class":73},[33,1531,1532],{"class":35,"line":96},[33,1533,348],{"class":73},[350,1535,1537],{"id":1536},"drag-and-drop-the-silent-non-fire","Drag-and-Drop — The Silent Non-Fire",[19,1539,1541],{"filename":1540,"language":22},"prevent_default_dnd.js",[24,1542,1544],{"className":26,"code":1543,"language":22,"meta":28,"style":28},"function DropZone({ onDrop }) {\n  function handleDragOver(event) {\n    event.preventDefault()\n    \u002F\u002F CRITICAL: without preventDefault on dragover, the browser's default\n    \u002F\u002F behavior is \"do not allow drop here\" — onDrop will NEVER fire.\n    \u002F\u002F This is the single most common \"my drop handler doesn't work\" cause.\n  }\n  function handleDrop(event) {\n    event.preventDefault()\n    const files = Array.from(event.dataTransfer.files)\n    onDrop(files)\n  }\n  return \u003Cdiv onDragOver={handleDragOver} onDrop={handleDrop}>Drop files here\u003C\u002Fdiv>\n}\n",[30,1545,1546,1560,1573,1581,1586,1591,1596,1600,1613,1621,1639,1647,1651,1679],{"__ignoreMap":28},[33,1547,1548,1550,1553,1555,1558],{"class":35,"line":36},[33,1549,66],{"class":65},[33,1551,1552],{"class":69}," DropZone",[33,1554,392],{"class":73},[33,1556,1557],{"class":89},"onDrop",[33,1559,398],{"class":73},[33,1561,1562,1564,1567,1569,1571],{"class":35,"line":43},[33,1563,80],{"class":65},[33,1565,1566],{"class":69}," handleDragOver",[33,1568,86],{"class":73},[33,1570,90],{"class":89},[33,1572,93],{"class":73},[33,1574,1575,1577,1579],{"class":35,"line":49},[33,1576,105],{"class":73},[33,1578,108],{"class":69},[33,1580,180],{"class":73},[33,1582,1583],{"class":35,"line":55},[33,1584,1585],{"class":39},"    \u002F\u002F CRITICAL: without preventDefault on dragover, the browser's default\n",[33,1587,1588],{"class":35,"line":62},[33,1589,1590],{"class":39},"    \u002F\u002F behavior is \"do not allow drop here\" — onDrop will NEVER fire.\n",[33,1592,1593],{"class":35,"line":77},[33,1594,1595],{"class":39},"    \u002F\u002F This is the single most common \"my drop handler doesn't work\" cause.\n",[33,1597,1598],{"class":35,"line":96},[33,1599,242],{"class":73},[33,1601,1602,1604,1607,1609,1611],{"class":35,"line":102},[33,1603,80],{"class":65},[33,1605,1606],{"class":69}," handleDrop",[33,1608,86],{"class":73},[33,1610,90],{"class":89},[33,1612,93],{"class":73},[33,1614,1615,1617,1619],{"class":35,"line":117},[33,1616,105],{"class":73},[33,1618,108],{"class":69},[33,1620,180],{"class":73},[33,1622,1623,1625,1628,1630,1633,1636],{"class":35,"line":131},[33,1624,191],{"class":65},[33,1626,1627],{"class":194}," files",[33,1629,198],{"class":65},[33,1631,1632],{"class":73}," Array.",[33,1634,1635],{"class":69},"from",[33,1637,1638],{"class":73},"(event.dataTransfer.files)\n",[33,1640,1641,1644],{"class":35,"line":140},[33,1642,1643],{"class":69},"    onDrop",[33,1645,1646],{"class":73},"(files)\n",[33,1648,1649],{"class":35,"line":149},[33,1650,242],{"class":73},[33,1652,1653,1655,1657,1660,1663,1665,1668,1670,1672,1675,1677],{"class":35,"line":158},[33,1654,253],{"class":65},[33,1656,708],{"class":73},[33,1658,1659],{"class":265},"div",[33,1661,1662],{"class":69}," onDragOver",[33,1664,272],{"class":65},[33,1666,1667],{"class":73},"{handleDragOver} ",[33,1669,1557],{"class":69},[33,1671,272],{"class":65},[33,1673,1674],{"class":73},"{handleDrop}>Drop files here\u003C\u002F",[33,1676,1659],{"class":265},[33,1678,326],{"class":73},[33,1680,1681],{"class":35,"line":172},[33,1682,348],{"class":73},[14,1684,1686,1688],{"id":1685},"stoppropagation-and-event-bubbling",[30,1687,122],{}," and Event Bubbling",[19,1690,1692],{"filename":1691,"language":22},"stop_propagation_modal.js",[24,1693,1695],{"className":26,"code":1694,"language":22,"meta":28,"style":28},"\u002F\u002F React synthetic events bubble like native DOM events, following the same tree order.\n\u002F\u002F stopPropagation() prevents ancestor handlers from firing.\n\nfunction Modal({ onClose, children }) {\n  return (\n    \u003Cdiv className=\"overlay\" onClick={onClose}>\n      {\u002F* Without stopPropagation, clicking inside the modal body\n          bubbles up to the overlay's onClick and closes the modal. *\u002F}\n      \u003Cdiv className=\"modal-body\" onClick={(e) => e.stopPropagation()}>\n        {children}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F CAVEAT: stopPropagation blocks ALL ancestor listeners, not just the one\n\u002F\u002F you're avoiding. A modal's inner stopPropagation also prevents an analytics\n\u002F\u002F \"track all clicks\" listener higher in the tree from ever seeing that click.\n\u002F\u002F Use it deliberately, not reflexively — consider event.target checks instead\n\u002F\u002F if you only need to exclude specific elements.\n",[30,1696,1697,1702,1707,1711,1729,1735,1756,1764,1771,1804,1809,1818,1826,1830,1834,1838,1843,1848,1853,1858],{"__ignoreMap":28},[33,1698,1699],{"class":35,"line":36},[33,1700,1701],{"class":39},"\u002F\u002F React synthetic events bubble like native DOM events, following the same tree order.\n",[33,1703,1704],{"class":35,"line":43},[33,1705,1706],{"class":39},"\u002F\u002F stopPropagation() prevents ancestor handlers from firing.\n",[33,1708,1709],{"class":35,"line":49},[33,1710,59],{"emptyLinePlaceholder":58},[33,1712,1713,1715,1718,1720,1723,1725,1727],{"class":35,"line":55},[33,1714,66],{"class":65},[33,1716,1717],{"class":69}," Modal",[33,1719,392],{"class":73},[33,1721,1722],{"class":89},"onClose",[33,1724,696],{"class":73},[33,1726,1459],{"class":89},[33,1728,398],{"class":73},[33,1730,1731,1733],{"class":35,"line":62},[33,1732,253],{"class":65},[33,1734,256],{"class":73},[33,1736,1737,1739,1741,1744,1746,1749,1751,1753],{"class":35,"line":77},[33,1738,262],{"class":73},[33,1740,1659],{"class":265},[33,1742,1743],{"class":69}," className",[33,1745,272],{"class":65},[33,1747,1748],{"class":215},"\"overlay\"",[33,1750,413],{"class":69},[33,1752,272],{"class":65},[33,1754,1755],{"class":73},"{onClose}>\n",[33,1757,1758,1761],{"class":35,"line":96},[33,1759,1760],{"class":73},"      {",[33,1762,1763],{"class":39},"\u002F* Without stopPropagation, clicking inside the modal body\n",[33,1765,1766,1769],{"class":35,"line":102},[33,1767,1768],{"class":39},"          bubbles up to the overlay's onClick and closes the modal. *\u002F",[33,1770,348],{"class":73},[33,1772,1773,1775,1777,1779,1781,1784,1786,1788,1790,1792,1794,1796,1799,1801],{"class":35,"line":117},[33,1774,281],{"class":73},[33,1776,1659],{"class":265},[33,1778,1743],{"class":69},[33,1780,272],{"class":65},[33,1782,1783],{"class":215},"\"modal-body\"",[33,1785,413],{"class":69},[33,1787,272],{"class":65},[33,1789,418],{"class":73},[33,1791,421],{"class":89},[33,1793,424],{"class":73},[33,1795,427],{"class":65},[33,1797,1798],{"class":73}," e.",[33,1800,122],{"class":69},[33,1802,1803],{"class":73},"()}>\n",[33,1805,1806],{"class":35,"line":131},[33,1807,1808],{"class":73},"        {children}\n",[33,1810,1811,1814,1816],{"class":35,"line":140},[33,1812,1813],{"class":73},"      \u003C\u002F",[33,1815,1659],{"class":265},[33,1817,326],{"class":73},[33,1819,1820,1822,1824],{"class":35,"line":149},[33,1821,332],{"class":73},[33,1823,1659],{"class":265},[33,1825,326],{"class":73},[33,1827,1828],{"class":35,"line":158},[33,1829,342],{"class":73},[33,1831,1832],{"class":35,"line":172},[33,1833,348],{"class":73},[33,1835,1836],{"class":35,"line":183},[33,1837,59],{"emptyLinePlaceholder":58},[33,1839,1840],{"class":35,"line":188},[33,1841,1842],{"class":39},"\u002F\u002F CAVEAT: stopPropagation blocks ALL ancestor listeners, not just the one\n",[33,1844,1845],{"class":35,"line":222},[33,1846,1847],{"class":39},"\u002F\u002F you're avoiding. A modal's inner stopPropagation also prevents an analytics\n",[33,1849,1850],{"class":35,"line":239},[33,1851,1852],{"class":39},"\u002F\u002F \"track all clicks\" listener higher in the tree from ever seeing that click.\n",[33,1854,1855],{"class":35,"line":245},[33,1856,1857],{"class":39},"\u002F\u002F Use it deliberately, not reflexively — consider event.target checks instead\n",[33,1859,1860],{"class":35,"line":250},[33,1861,1862],{"class":39},"\u002F\u002F if you only need to exclude specific elements.\n",[19,1864,1866],{"filename":1865,"language":22},"stop_propagation_alternative.js",[24,1867,1869],{"className":26,"code":1868,"language":22,"meta":28,"style":28},"\u002F\u002F ALTERNATIVE: check the target instead of stopping propagation.\n\u002F\u002F This lets analytics\u002Fother ancestors still see the event.\n\nfunction Modal({ onClose, children }) {\n  return (\n    \u003Cdiv className=\"overlay\" onClick={(e) => {\n      \u002F\u002F Only close if the click was on the overlay ITSELF, not a child\n      if (e.target === e.currentTarget) onClose()\n    }}>\n      \u003Cdiv className=\"modal-body\">\n        {children}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  )\n}\n\u002F\u002F This pattern preserves bubbling for other listeners while achieving\n\u002F\u002F the same \"click outside to close\" behavior.\n",[30,1870,1871,1876,1881,1885,1901,1907,1933,1938,1956,1960,1974,1978,1986,1994,1998,2002,2007],{"__ignoreMap":28},[33,1872,1873],{"class":35,"line":36},[33,1874,1875],{"class":39},"\u002F\u002F ALTERNATIVE: check the target instead of stopping propagation.\n",[33,1877,1878],{"class":35,"line":43},[33,1879,1880],{"class":39},"\u002F\u002F This lets analytics\u002Fother ancestors still see the event.\n",[33,1882,1883],{"class":35,"line":49},[33,1884,59],{"emptyLinePlaceholder":58},[33,1886,1887,1889,1891,1893,1895,1897,1899],{"class":35,"line":55},[33,1888,66],{"class":65},[33,1890,1717],{"class":69},[33,1892,392],{"class":73},[33,1894,1722],{"class":89},[33,1896,696],{"class":73},[33,1898,1459],{"class":89},[33,1900,398],{"class":73},[33,1902,1903,1905],{"class":35,"line":62},[33,1904,253],{"class":65},[33,1906,256],{"class":73},[33,1908,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927,1929,1931],{"class":35,"line":77},[33,1910,262],{"class":73},[33,1912,1659],{"class":265},[33,1914,1743],{"class":69},[33,1916,272],{"class":65},[33,1918,1748],{"class":215},[33,1920,413],{"class":69},[33,1922,272],{"class":65},[33,1924,418],{"class":73},[33,1926,421],{"class":89},[33,1928,424],{"class":73},[33,1930,427],{"class":65},[33,1932,430],{"class":73},[33,1934,1935],{"class":35,"line":96},[33,1936,1937],{"class":39},"      \u002F\u002F Only close if the click was on the overlay ITSELF, not a child\n",[33,1939,1940,1943,1946,1949,1952,1954],{"class":35,"line":102},[33,1941,1942],{"class":65},"      if",[33,1944,1945],{"class":73}," (e.target ",[33,1947,1948],{"class":65},"===",[33,1950,1951],{"class":73}," e.currentTarget) ",[33,1953,1722],{"class":69},[33,1955,180],{"class":73},[33,1957,1958],{"class":35,"line":117},[33,1959,472],{"class":73},[33,1961,1962,1964,1966,1968,1970,1972],{"class":35,"line":131},[33,1963,281],{"class":73},[33,1965,1659],{"class":265},[33,1967,1743],{"class":69},[33,1969,272],{"class":65},[33,1971,1783],{"class":215},[33,1973,326],{"class":73},[33,1975,1976],{"class":35,"line":140},[33,1977,1808],{"class":73},[33,1979,1980,1982,1984],{"class":35,"line":149},[33,1981,1813],{"class":73},[33,1983,1659],{"class":265},[33,1985,326],{"class":73},[33,1987,1988,1990,1992],{"class":35,"line":158},[33,1989,332],{"class":73},[33,1991,1659],{"class":265},[33,1993,326],{"class":73},[33,1995,1996],{"class":35,"line":172},[33,1997,342],{"class":73},[33,1999,2000],{"class":35,"line":183},[33,2001,348],{"class":73},[33,2003,2004],{"class":35,"line":188},[33,2005,2006],{"class":39},"\u002F\u002F This pattern preserves bubbling for other listeners while achieving\n",[33,2008,2009],{"class":35,"line":222},[33,2010,2011],{"class":39},"\u002F\u002F the same \"click outside to close\" behavior.\n",[14,2013,2015],{"id":2014},"debouncing-high-frequency-handlers","Debouncing High-Frequency Handlers",[19,2017,2019],{"filename":2018,"language":22},"debounce_search.js",[24,2020,2022],{"className":26,"code":2021,"language":22,"meta":28,"style":28},"\u002F\u002F High-frequency events (onScroll, onMouseMove, onChange hitting an API)\n\u002F\u002F need debouncing to avoid overwhelming the app or network.\n\nfunction SearchBox({ onSearch }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    if (query === '') return  \u002F\u002F don't search on empty\n    const timeoutId = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeoutId)  \u002F\u002F cancels the previous timer on every keystroke\n    \u002F\u002F Cleanup runs BEFORE the next effect: every keystroke resets the 300ms timer.\n    \u002F\u002F The API call only fires 300ms after the user STOPS typing.\n  }, [query, onSearch])\n\n  return \u003Cinput value={query} onChange={e => setQuery(e.target.value)} placeholder=\"Search…\" \u002F>\n}\n\u002F\u002F This pattern (debounce via useEffect + setTimeout + cleanup) is covered in\n\u002F\u002F full in Chapter 6 — included here as the standard fix for \"handler fires too often.\"\n",[30,2023,2024,2029,2034,2038,2052,2078,2082,2094,2115,2142,2160,2165,2170,2175,2179,2219,2223,2228],{"__ignoreMap":28},[33,2025,2026],{"class":35,"line":36},[33,2027,2028],{"class":39},"\u002F\u002F High-frequency events (onScroll, onMouseMove, onChange hitting an API)\n",[33,2030,2031],{"class":35,"line":43},[33,2032,2033],{"class":39},"\u002F\u002F need debouncing to avoid overwhelming the app or network.\n",[33,2035,2036],{"class":35,"line":49},[33,2037,59],{"emptyLinePlaceholder":58},[33,2039,2040,2042,2045,2047,2050],{"class":35,"line":55},[33,2041,66],{"class":65},[33,2043,2044],{"class":69}," SearchBox",[33,2046,392],{"class":73},[33,2048,2049],{"class":89},"onSearch",[33,2051,398],{"class":73},[33,2053,2054,2056,2058,2061,2063,2066,2068,2070,2072,2074,2076],{"class":35,"line":62},[33,2055,1188],{"class":65},[33,2057,1191],{"class":73},[33,2059,2060],{"class":194},"query",[33,2062,696],{"class":73},[33,2064,2065],{"class":194},"setQuery",[33,2067,1202],{"class":73},[33,2069,272],{"class":65},[33,2071,1207],{"class":69},[33,2073,86],{"class":73},[33,2075,1212],{"class":215},[33,2077,219],{"class":73},[33,2079,2080],{"class":35,"line":77},[33,2081,59],{"emptyLinePlaceholder":58},[33,2083,2084,2087,2090,2092],{"class":35,"line":96},[33,2085,2086],{"class":69},"  useEffect",[33,2088,2089],{"class":73},"(() ",[33,2091,427],{"class":65},[33,2093,430],{"class":73},[33,2095,2096,2099,2102,2104,2107,2109,2112],{"class":35,"line":102},[33,2097,2098],{"class":65},"    if",[33,2100,2101],{"class":73}," (query ",[33,2103,1948],{"class":65},[33,2105,2106],{"class":215}," ''",[33,2108,424],{"class":73},[33,2110,2111],{"class":65},"return",[33,2113,2114],{"class":39},"  \u002F\u002F don't search on empty\n",[33,2116,2117,2119,2122,2124,2127,2129,2131,2134,2137,2140],{"class":35,"line":117},[33,2118,191],{"class":65},[33,2120,2121],{"class":194}," timeoutId",[33,2123,198],{"class":65},[33,2125,2126],{"class":69}," setTimeout",[33,2128,2089],{"class":73},[33,2130,427],{"class":65},[33,2132,2133],{"class":69}," onSearch",[33,2135,2136],{"class":73},"(query), ",[33,2138,2139],{"class":194},"300",[33,2141,219],{"class":73},[33,2143,2144,2147,2149,2151,2154,2157],{"class":35,"line":131},[33,2145,2146],{"class":65},"    return",[33,2148,843],{"class":73},[33,2150,427],{"class":65},[33,2152,2153],{"class":69}," clearTimeout",[33,2155,2156],{"class":73},"(timeoutId)  ",[33,2158,2159],{"class":39},"\u002F\u002F cancels the previous timer on every keystroke\n",[33,2161,2162],{"class":35,"line":140},[33,2163,2164],{"class":39},"    \u002F\u002F Cleanup runs BEFORE the next effect: every keystroke resets the 300ms timer.\n",[33,2166,2167],{"class":35,"line":149},[33,2168,2169],{"class":39},"    \u002F\u002F The API call only fires 300ms after the user STOPS typing.\n",[33,2171,2172],{"class":35,"line":158},[33,2173,2174],{"class":73},"  }, [query, onSearch])\n",[33,2176,2177],{"class":35,"line":172},[33,2178,59],{"emptyLinePlaceholder":58},[33,2180,2181,2183,2185,2187,2189,2191,2194,2196,2198,2200,2202,2204,2207,2209,2212,2214,2217],{"class":35,"line":183},[33,2182,253],{"class":65},[33,2184,708],{"class":73},[33,2186,284],{"class":265},[33,2188,1321],{"class":69},[33,2190,272],{"class":65},[33,2192,2193],{"class":73},"{query} ",[33,2195,1329],{"class":69},[33,2197,272],{"class":65},[33,2199,717],{"class":73},[33,2201,421],{"class":89},[33,2203,997],{"class":65},[33,2205,2206],{"class":69}," setQuery",[33,2208,1343],{"class":73},[33,2210,2211],{"class":69},"placeholder",[33,2213,272],{"class":65},[33,2215,2216],{"class":215},"\"Search…\"",[33,2218,303],{"class":73},[33,2220,2221],{"class":35,"line":188},[33,2222,348],{"class":73},[33,2224,2225],{"class":35,"line":222},[33,2226,2227],{"class":39},"\u002F\u002F This pattern (debounce via useEffect + setTimeout + cleanup) is covered in\n",[33,2229,2230],{"class":35,"line":239},[33,2231,2232],{"class":39},"\u002F\u002F full in Chapter 6 — included here as the standard fix for \"handler fires too often.\"\n",[14,2234,2236],{"id":2235},"custom-hooks-for-event-logic","Custom Hooks for Event Logic",[19,2238,2240],{"filename":2239,"language":22},"use_event_listener.js",[24,2241,2243],{"className":26,"code":2242,"language":22,"meta":28,"style":28},"import { useEffect, useRef } from 'react'\n\n\u002F\u002F PRODUCTION PATTERN: reusable event listener hook for document\u002Fwindow-level events\n\u002F\u002F that need proper cleanup and optional element targeting.\n\nfunction useEventListener(eventName, handler, element = window) {\n  const savedHandler = useRef(handler)\n\n  \u002F\u002F Update the ref each render so the effect always has the latest handler\n  \u002F\u002F without needing to re-attach the listener (which would cause flicker).\n  useEffect(() => {\n    savedHandler.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    \u002F\u002F SSR guard — window doesn't exist during server rendering\n    if (!(element && element.addEventListener)) return\n\n    const eventListener = (event) => savedHandler.current(event)\n    element.addEventListener(eventName, eventListener)\n    return () => element.removeEventListener(eventName, eventListener)\n  }, [eventName, element])\n}\n\n\u002F\u002F Usage:\n\u002F\u002F useEventListener('keydown', handleEscape)        \u002F\u002F on window\n\u002F\u002F useEventListener('click', handleClick, ref.current) \u002F\u002F on a specific element\n",[30,2244,2245,2258,2262,2267,2272,2276,2303,2318,2322,2327,2332,2342,2352,2357,2361,2371,2376,2398,2402,2428,2439,2455,2460,2464,2468,2473,2481],{"__ignoreMap":28},[33,2246,2247,2250,2253,2255],{"class":35,"line":36},[33,2248,2249],{"class":65},"import",[33,2251,2252],{"class":73}," { useEffect, useRef } ",[33,2254,1635],{"class":65},[33,2256,2257],{"class":215}," 'react'\n",[33,2259,2260],{"class":35,"line":43},[33,2261,59],{"emptyLinePlaceholder":58},[33,2263,2264],{"class":35,"line":49},[33,2265,2266],{"class":39},"\u002F\u002F PRODUCTION PATTERN: reusable event listener hook for document\u002Fwindow-level events\n",[33,2268,2269],{"class":35,"line":55},[33,2270,2271],{"class":39},"\u002F\u002F that need proper cleanup and optional element targeting.\n",[33,2273,2274],{"class":35,"line":62},[33,2275,59],{"emptyLinePlaceholder":58},[33,2277,2278,2280,2283,2285,2288,2290,2293,2295,2298,2300],{"class":35,"line":77},[33,2279,66],{"class":65},[33,2281,2282],{"class":69}," useEventListener",[33,2284,86],{"class":73},[33,2286,2287],{"class":89},"eventName",[33,2289,696],{"class":73},[33,2291,2292],{"class":89},"handler",[33,2294,696],{"class":73},[33,2296,2297],{"class":89},"element",[33,2299,198],{"class":65},[33,2301,2302],{"class":73}," window) {\n",[33,2304,2305,2307,2310,2312,2315],{"class":35,"line":96},[33,2306,1188],{"class":65},[33,2308,2309],{"class":194}," savedHandler",[33,2311,198],{"class":65},[33,2313,2314],{"class":69}," useRef",[33,2316,2317],{"class":73},"(handler)\n",[33,2319,2320],{"class":35,"line":102},[33,2321,59],{"emptyLinePlaceholder":58},[33,2323,2324],{"class":35,"line":117},[33,2325,2326],{"class":39},"  \u002F\u002F Update the ref each render so the effect always has the latest handler\n",[33,2328,2329],{"class":35,"line":131},[33,2330,2331],{"class":39},"  \u002F\u002F without needing to re-attach the listener (which would cause flicker).\n",[33,2333,2334,2336,2338,2340],{"class":35,"line":140},[33,2335,2086],{"class":69},[33,2337,2089],{"class":73},[33,2339,427],{"class":65},[33,2341,430],{"class":73},[33,2343,2344,2347,2349],{"class":35,"line":149},[33,2345,2346],{"class":73},"    savedHandler.current ",[33,2348,272],{"class":65},[33,2350,2351],{"class":73}," handler\n",[33,2353,2354],{"class":35,"line":158},[33,2355,2356],{"class":73},"  }, [handler])\n",[33,2358,2359],{"class":35,"line":172},[33,2360,59],{"emptyLinePlaceholder":58},[33,2362,2363,2365,2367,2369],{"class":35,"line":183},[33,2364,2086],{"class":69},[33,2366,2089],{"class":73},[33,2368,427],{"class":65},[33,2370,430],{"class":73},[33,2372,2373],{"class":35,"line":188},[33,2374,2375],{"class":39},"    \u002F\u002F SSR guard — window doesn't exist during server rendering\n",[33,2377,2378,2380,2383,2386,2389,2392,2395],{"class":35,"line":222},[33,2379,2098],{"class":65},[33,2381,2382],{"class":73}," (",[33,2384,2385],{"class":65},"!",[33,2387,2388],{"class":73},"(element ",[33,2390,2391],{"class":65},"&&",[33,2393,2394],{"class":73}," element.addEventListener)) ",[33,2396,2397],{"class":65},"return\n",[33,2399,2400],{"class":35,"line":239},[33,2401,59],{"emptyLinePlaceholder":58},[33,2403,2404,2406,2409,2411,2413,2415,2417,2419,2422,2425],{"class":35,"line":245},[33,2405,191],{"class":65},[33,2407,2408],{"class":69}," eventListener",[33,2410,198],{"class":65},[33,2412,2382],{"class":73},[33,2414,90],{"class":89},[33,2416,424],{"class":73},[33,2418,427],{"class":65},[33,2420,2421],{"class":73}," savedHandler.",[33,2423,2424],{"class":69},"current",[33,2426,2427],{"class":73},"(event)\n",[33,2429,2430,2433,2436],{"class":35,"line":250},[33,2431,2432],{"class":73},"    element.",[33,2434,2435],{"class":69},"addEventListener",[33,2437,2438],{"class":73},"(eventName, eventListener)\n",[33,2440,2441,2443,2445,2447,2450,2453],{"class":35,"line":259},[33,2442,2146],{"class":65},[33,2444,843],{"class":73},[33,2446,427],{"class":65},[33,2448,2449],{"class":73}," element.",[33,2451,2452],{"class":69},"removeEventListener",[33,2454,2438],{"class":73},[33,2456,2457],{"class":35,"line":278},[33,2458,2459],{"class":73},"  }, [eventName, element])\n",[33,2461,2462],{"class":35,"line":306},[33,2463,348],{"class":73},[33,2465,2466],{"class":35,"line":329},[33,2467,59],{"emptyLinePlaceholder":58},[33,2469,2470],{"class":35,"line":339},[33,2471,2472],{"class":39},"\u002F\u002F Usage:\n",[33,2474,2475,2478],{"class":35,"line":345},[33,2476,2477],{"class":39},"\u002F\u002F useEventListener('keydown', handleEscape)",[33,2479,2480],{"class":39},"        \u002F\u002F on window\n",[33,2482,2484,2487],{"class":35,"line":2483},27,[33,2485,2486],{"class":39},"\u002F\u002F useEventListener('click', handleClick, ref.current)",[33,2488,2489],{"class":39}," \u002F\u002F on a specific element\n",[14,2491,2493],{"id":2492},"tips-tricks","💡 Tips & Tricks",[19,2495,2497],{"filename":2496,"language":22},"tips.js",[24,2498,2500],{"className":26,"code":2499,"language":22,"meta":28,"style":28},"\u002F\u002F [Idiom] Prefer event.currentTarget over event.target when you need \"the element\n\u002F\u002F this handler is attached to\" — especially for delegated click handlers on\n\u002F\u002F elements with nested icons\u002Fspans. target can be any descendant the user clicked.\n\n\u002F\u002F [Debug] If a form mysteriously reloads the page or app state resets on submit,\n\u002F\u002F check for a missing event.preventDefault() in onSubmit FIRST. It's the single\n\u002F\u002F most common cause of \"my app just refreshed for no reason.\"\n\n\u002F\u002F [Performance] Inline arrow functions in onClick={() => doThing(id)} inside list\n\u002F\u002F items are NOT a performance problem for the vast majority of apps. Don't reach\n\u002F\u002F for useCallback until you've measured a re-render problem with the Profiler.\n\n\u002F\u002F [Idiom] Use onMouseEnter\u002FonMouseLeave instead of onMouseOver\u002FonMouseOut for hover\n\u002F\u002F UI — Enter\u002FLeave doesn't bubble through children, avoiding flicker when the\n\u002F\u002F pointer crosses internal element boundaries within the hovered area.\n\n\u002F\u002F [Debug] When onKeyDown handlers \"miss\" certain keys, check whether the element\n\u002F\u002F is focusable — keyboard events require tabIndex={0} or a naturally focusable\n\u002F\u002F element (button, input, a[href]). A plain div won't receive keyboard events.\n\n\u002F\u002F [Idiom] For Ctrl\u002FCmd+S \"save\" shortcuts, use onKeyDown on a focusable container\n\u002F\u002F or document-level listener — and always preventDefault to stop the browser's\n\u002F\u002F native \"Save Page\" dialog.\n",[30,2501,2502,2507,2512,2517,2521,2526,2531,2536,2540,2545,2550,2555,2559,2564,2569,2574,2578,2583,2588,2593,2597,2602,2607],{"__ignoreMap":28},[33,2503,2504],{"class":35,"line":36},[33,2505,2506],{"class":39},"\u002F\u002F [Idiom] Prefer event.currentTarget over event.target when you need \"the element\n",[33,2508,2509],{"class":35,"line":43},[33,2510,2511],{"class":39},"\u002F\u002F this handler is attached to\" — especially for delegated click handlers on\n",[33,2513,2514],{"class":35,"line":49},[33,2515,2516],{"class":39},"\u002F\u002F elements with nested icons\u002Fspans. target can be any descendant the user clicked.\n",[33,2518,2519],{"class":35,"line":55},[33,2520,59],{"emptyLinePlaceholder":58},[33,2522,2523],{"class":35,"line":62},[33,2524,2525],{"class":39},"\u002F\u002F [Debug] If a form mysteriously reloads the page or app state resets on submit,\n",[33,2527,2528],{"class":35,"line":77},[33,2529,2530],{"class":39},"\u002F\u002F check for a missing event.preventDefault() in onSubmit FIRST. It's the single\n",[33,2532,2533],{"class":35,"line":96},[33,2534,2535],{"class":39},"\u002F\u002F most common cause of \"my app just refreshed for no reason.\"\n",[33,2537,2538],{"class":35,"line":102},[33,2539,59],{"emptyLinePlaceholder":58},[33,2541,2542],{"class":35,"line":117},[33,2543,2544],{"class":39},"\u002F\u002F [Performance] Inline arrow functions in onClick={() => doThing(id)} inside list\n",[33,2546,2547],{"class":35,"line":131},[33,2548,2549],{"class":39},"\u002F\u002F items are NOT a performance problem for the vast majority of apps. Don't reach\n",[33,2551,2552],{"class":35,"line":140},[33,2553,2554],{"class":39},"\u002F\u002F for useCallback until you've measured a re-render problem with the Profiler.\n",[33,2556,2557],{"class":35,"line":149},[33,2558,59],{"emptyLinePlaceholder":58},[33,2560,2561],{"class":35,"line":158},[33,2562,2563],{"class":39},"\u002F\u002F [Idiom] Use onMouseEnter\u002FonMouseLeave instead of onMouseOver\u002FonMouseOut for hover\n",[33,2565,2566],{"class":35,"line":172},[33,2567,2568],{"class":39},"\u002F\u002F UI — Enter\u002FLeave doesn't bubble through children, avoiding flicker when the\n",[33,2570,2571],{"class":35,"line":183},[33,2572,2573],{"class":39},"\u002F\u002F pointer crosses internal element boundaries within the hovered area.\n",[33,2575,2576],{"class":35,"line":188},[33,2577,59],{"emptyLinePlaceholder":58},[33,2579,2580],{"class":35,"line":222},[33,2581,2582],{"class":39},"\u002F\u002F [Debug] When onKeyDown handlers \"miss\" certain keys, check whether the element\n",[33,2584,2585],{"class":35,"line":239},[33,2586,2587],{"class":39},"\u002F\u002F is focusable — keyboard events require tabIndex={0} or a naturally focusable\n",[33,2589,2590],{"class":35,"line":245},[33,2591,2592],{"class":39},"\u002F\u002F element (button, input, a[href]). A plain div won't receive keyboard events.\n",[33,2594,2595],{"class":35,"line":250},[33,2596,59],{"emptyLinePlaceholder":58},[33,2598,2599],{"class":35,"line":259},[33,2600,2601],{"class":39},"\u002F\u002F [Idiom] For Ctrl\u002FCmd+S \"save\" shortcuts, use onKeyDown on a focusable container\n",[33,2603,2604],{"class":35,"line":278},[33,2605,2606],{"class":39},"\u002F\u002F or document-level listener — and always preventDefault to stop the browser's\n",[33,2608,2609],{"class":35,"line":306},[33,2610,2611],{"class":39},"\u002F\u002F native \"Save Page\" dialog.\n",[14,2613,2615],{"id":2614},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,2617,2619],{"filename":2618,"language":22},"edge_cases.js",[24,2620,2622],{"className":26,"code":2621,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] Forgetting event.preventDefault() in onSubmit reloads the page and\n\u002F\u002F wipes ALL state. The browser's default form behavior is a full navigation;\n\u002F\u002F React state doesn't survive it. The bug often looks unrelated to the form\n\u002F\u002F since the whole app remounts from scratch.\n\n\u002F\u002F [Gotcha] onClick={fn()} calls the function DURING RENDER, not on click.\n\u002F\u002F A very easy typo: onClick={handleDelete(id)} instead of onClick={() => handleDelete(id)}\n\u002F\u002F → handleDelete runs immediately on every render, and its return value (undefined)\n\u002F\u002F silently makes the button inert with NO error.\n\n\u002F\u002F [Gotcha] onChange in React fires on EVERY keystroke, not on blur\u002Fcommit like\n\u002F\u002F plain HTML. Code ported from a non-React mental model expecting change to fire\n\u002F\u002F only on blur will see far more invocations than expected. This is deliberate —\n\u002F\u002F it underpins the controlled-input model (Chapter 14).\n\n\u002F\u002F [Gotcha] stopPropagation() inside a child can silently break unrelated ancestor\n\u002F\u002F logic — a modal's inner stopPropagation also prevents an analytics \"track all\n\u002F\u002F clicks\" listener higher in the tree from ever seeing that click. Consider\n\u002F\u002F target === currentTarget checks as a less destructive alternative.\n\n\u002F\u002F [Gotcha] Synthetic event objects were POOLED in React ≤16 and reused across\n\u002F\u002F events. Accessing event.type asynchronously (inside a setTimeout after the\n\u002F\u002F handler returns) threw or read null in React 16 and earlier. React 17+ removed\n\u002F\u002F event pooling — but this still explains \"works in new project, breaks in old\n\u002F\u002F codebase\" bugs. If you must support React 16, call event.persist() or extract\n\u002F\u002F the values you need before the async boundary.\n\n\u002F\u002F [Gotcha] React's synthetic event system does NOT support capture-phase\n\u002F\u002F listeners via the standard addEventListener('click', fn, true) API. React\n\u002F\u002F uses the onClickCapture prop convention instead:\n\u002F\u002F   \u003Cdiv onClickCapture={handleCapture}> — fires during capture, before bubbling.\n",[30,2623,2624,2629,2634,2639,2644,2648,2653,2658,2663,2668,2672,2677,2682,2687,2692,2696,2701,2706,2711,2716,2720,2725,2730,2735,2740,2745,2750,2754,2760,2766,2772],{"__ignoreMap":28},[33,2625,2626],{"class":35,"line":36},[33,2627,2628],{"class":39},"\u002F\u002F [Gotcha] Forgetting event.preventDefault() in onSubmit reloads the page and\n",[33,2630,2631],{"class":35,"line":43},[33,2632,2633],{"class":39},"\u002F\u002F wipes ALL state. The browser's default form behavior is a full navigation;\n",[33,2635,2636],{"class":35,"line":49},[33,2637,2638],{"class":39},"\u002F\u002F React state doesn't survive it. The bug often looks unrelated to the form\n",[33,2640,2641],{"class":35,"line":55},[33,2642,2643],{"class":39},"\u002F\u002F since the whole app remounts from scratch.\n",[33,2645,2646],{"class":35,"line":62},[33,2647,59],{"emptyLinePlaceholder":58},[33,2649,2650],{"class":35,"line":77},[33,2651,2652],{"class":39},"\u002F\u002F [Gotcha] onClick={fn()} calls the function DURING RENDER, not on click.\n",[33,2654,2655],{"class":35,"line":96},[33,2656,2657],{"class":39},"\u002F\u002F A very easy typo: onClick={handleDelete(id)} instead of onClick={() => handleDelete(id)}\n",[33,2659,2660],{"class":35,"line":102},[33,2661,2662],{"class":39},"\u002F\u002F → handleDelete runs immediately on every render, and its return value (undefined)\n",[33,2664,2665],{"class":35,"line":117},[33,2666,2667],{"class":39},"\u002F\u002F silently makes the button inert with NO error.\n",[33,2669,2670],{"class":35,"line":131},[33,2671,59],{"emptyLinePlaceholder":58},[33,2673,2674],{"class":35,"line":140},[33,2675,2676],{"class":39},"\u002F\u002F [Gotcha] onChange in React fires on EVERY keystroke, not on blur\u002Fcommit like\n",[33,2678,2679],{"class":35,"line":149},[33,2680,2681],{"class":39},"\u002F\u002F plain HTML. Code ported from a non-React mental model expecting change to fire\n",[33,2683,2684],{"class":35,"line":158},[33,2685,2686],{"class":39},"\u002F\u002F only on blur will see far more invocations than expected. This is deliberate —\n",[33,2688,2689],{"class":35,"line":172},[33,2690,2691],{"class":39},"\u002F\u002F it underpins the controlled-input model (Chapter 14).\n",[33,2693,2694],{"class":35,"line":183},[33,2695,59],{"emptyLinePlaceholder":58},[33,2697,2698],{"class":35,"line":188},[33,2699,2700],{"class":39},"\u002F\u002F [Gotcha] stopPropagation() inside a child can silently break unrelated ancestor\n",[33,2702,2703],{"class":35,"line":222},[33,2704,2705],{"class":39},"\u002F\u002F logic — a modal's inner stopPropagation also prevents an analytics \"track all\n",[33,2707,2708],{"class":35,"line":239},[33,2709,2710],{"class":39},"\u002F\u002F clicks\" listener higher in the tree from ever seeing that click. Consider\n",[33,2712,2713],{"class":35,"line":245},[33,2714,2715],{"class":39},"\u002F\u002F target === currentTarget checks as a less destructive alternative.\n",[33,2717,2718],{"class":35,"line":250},[33,2719,59],{"emptyLinePlaceholder":58},[33,2721,2722],{"class":35,"line":259},[33,2723,2724],{"class":39},"\u002F\u002F [Gotcha] Synthetic event objects were POOLED in React ≤16 and reused across\n",[33,2726,2727],{"class":35,"line":278},[33,2728,2729],{"class":39},"\u002F\u002F events. Accessing event.type asynchronously (inside a setTimeout after the\n",[33,2731,2732],{"class":35,"line":306},[33,2733,2734],{"class":39},"\u002F\u002F handler returns) threw or read null in React 16 and earlier. React 17+ removed\n",[33,2736,2737],{"class":35,"line":329},[33,2738,2739],{"class":39},"\u002F\u002F event pooling — but this still explains \"works in new project, breaks in old\n",[33,2741,2742],{"class":35,"line":339},[33,2743,2744],{"class":39},"\u002F\u002F codebase\" bugs. If you must support React 16, call event.persist() or extract\n",[33,2746,2747],{"class":35,"line":345},[33,2748,2749],{"class":39},"\u002F\u002F the values you need before the async boundary.\n",[33,2751,2752],{"class":35,"line":2483},[33,2753,59],{"emptyLinePlaceholder":58},[33,2755,2757],{"class":35,"line":2756},28,[33,2758,2759],{"class":39},"\u002F\u002F [Gotcha] React's synthetic event system does NOT support capture-phase\n",[33,2761,2763],{"class":35,"line":2762},29,[33,2764,2765],{"class":39},"\u002F\u002F listeners via the standard addEventListener('click', fn, true) API. React\n",[33,2767,2769],{"class":35,"line":2768},30,[33,2770,2771],{"class":39},"\u002F\u002F uses the onClickCapture prop convention instead:\n",[33,2773,2775],{"class":35,"line":2774},31,[33,2776,2777],{"class":39},"\u002F\u002F   \u003Cdiv onClickCapture={handleCapture}> — fires during capture, before bubbling.\n",[14,2779,2781],{"id":2780},"spot-the-bug","🧠 Spot the Bug",[2783,2784,2785],"p",{},"A \"select all\" checkbox is supposed to toggle every row, but clicking it does nothing — no errors, no console output.",[19,2787,2789],{"filename":2788,"language":22},"spot_the_bug.js",[24,2790,2792],{"className":26,"code":2791,"language":22,"meta":28,"style":28},"function SelectAllCheckbox({ onSelectAll }) {\n  return \u003Cinput type=\"checkbox\" onClick={onSelectAll(true)} \u002F>\n}\n",[30,2793,2794,2808,2839],{"__ignoreMap":28},[33,2795,2796,2798,2801,2803,2806],{"class":35,"line":36},[33,2797,66],{"class":65},[33,2799,2800],{"class":69}," SelectAllCheckbox",[33,2802,392],{"class":73},[33,2804,2805],{"class":89},"onSelectAll",[33,2807,398],{"class":73},[33,2809,2810,2812,2814,2816,2818,2820,2823,2825,2827,2829,2831,2833,2836],{"class":35,"line":43},[33,2811,253],{"class":65},[33,2813,708],{"class":73},[33,2815,284],{"class":265},[33,2817,295],{"class":69},[33,2819,272],{"class":65},[33,2821,2822],{"class":215},"\"checkbox\"",[33,2824,413],{"class":69},[33,2826,272],{"class":65},[33,2828,717],{"class":73},[33,2830,2805],{"class":69},[33,2832,86],{"class":73},[33,2834,2835],{"class":194},"true",[33,2837,2838],{"class":73},")} \u002F>\n",[33,2840,2841],{"class":35,"line":49},[33,2842,348],{"class":73},[2844,2845,2846,2850,2874,2880,2914],"details",{},[2847,2848,2849],"summary",{},"Answer",[2783,2851,2852,2855,2856,2860,2861,2863,2864,2867,2868,2870,2871,2873],{},[30,2853,2854],{},"onSelectAll(true)"," is ",[2857,2858,2859],"strong",{},"called immediately"," during render — it's an invocation, not a reference. Whatever ",[30,2862,2854],{}," returns (likely ",[30,2865,2866],{},"undefined",", if it's a void function) is what gets assigned to ",[30,2869,1520],{},". The checkbox's real click handler is ",[30,2872,2866],{},", so clicking it does nothing — while the \"select all\" logic already ran once during the initial render, without user interaction.",[2783,2875,2876,2879],{},[2857,2877,2878],{},"Fix",": wrap in an arrow function so it only executes when the event fires:",[24,2881,2883],{"className":26,"code":2882,"language":22,"meta":28,"style":28},"\u003Cinput type=\"checkbox\" onClick={() => onSelectAll(true)} \u002F>\n",[30,2884,2885],{"__ignoreMap":28},[33,2886,2887,2889,2891,2893,2895,2897,2899,2901,2903,2905,2908,2910,2912],{"class":35,"line":36},[33,2888,767],{"class":73},[33,2890,284],{"class":265},[33,2892,295],{"class":69},[33,2894,272],{"class":65},[33,2896,2822],{"class":215},[33,2898,413],{"class":69},[33,2900,272],{"class":65},[33,2902,802],{"class":73},[33,2904,427],{"class":65},[33,2906,2907],{"class":69}," onSelectAll",[33,2909,86],{"class":73},[33,2911,2835],{"class":194},[33,2913,2838],{"class":73},[2783,2915,2916,2919,2920,2924,2925,2928,2929,2932,2933,2936],{},[2857,2917,2918],{},"The lesson",": JSX event props need a function ",[2921,2922,2923],"em",{},"reference",", not a function ",[2921,2926,2927],{},"call",". Any handler that needs arguments must be wrapped: ",[30,2930,2931],{},"onClick={() => fn(arg)}",", not ",[30,2934,2935],{},"onClick={fn(arg)}",".",[14,2938,2940],{"id":2939},"key-takeaways","Key Takeaways",[19,2942,2944],{"filename":2943,"language":22},"key_takeaways.js",[24,2945,2947],{"className":26,"code":2946,"language":22,"meta":28,"style":28},"\u002F\u002F 1. React wraps native events in SyntheticEvent — same API (preventDefault,\n\u002F\u002F    stopPropagation, target, currentTarget) plus .nativeEvent for raw access.\n\u002F\u002F    event.target = actual click target (could be child); currentTarget = handler's element.\n\n\u002F\u002F 2. React 17+ attaches event listeners to the root container (not document).\n\u002F\u002F    Isolates multiple React trees and non-React widgets on the same page.\n\u002F\u002F    Event pooling was removed — SyntheticEvent objects are safe to access async.\n\n\u002F\u002F 3. Handler props need a FUNCTION REFERENCE, not an invocation.\n\u002F\u002F    onClick={fn}     → correct (reference, no args)\n\u002F\u002F    onClick={fn()}   → BUG: runs during render, assigns return value (undefined) to onClick\n\u002F\u002F    onClick={() => fn(arg)} → correct (arrow wrapper for args)\n\n\u002F\u002F 4. Missing preventDefault() on onSubmit triggers a full page reload — the\n\u002F\u002F    #1 React beginner bug. Also required on dragover for drop to work, and\n\u002F\u002F    on links for client-side routing.\n\n\u002F\u002F 5. onChange in React fires per keystroke (not on blur like plain HTML).\n\u002F\u002F    This underpins the controlled-input model (Chapter 14).\n\n\u002F\u002F 6. stopPropagation() blocks ALL ancestor listeners, not just the one you're\n\u002F\u002F    avoiding. Consider target === currentTarget checks for less destructive\n\u002F\u002F    \"click outside\" detection that preserves bubbling for other listeners.\n",[30,2948,2949,2954,2959,2964,2968,2973,2978,2983,2987,2992,2997,3002,3007,3011,3016,3021,3026,3030,3035,3040,3044,3049,3054],{"__ignoreMap":28},[33,2950,2951],{"class":35,"line":36},[33,2952,2953],{"class":39},"\u002F\u002F 1. React wraps native events in SyntheticEvent — same API (preventDefault,\n",[33,2955,2956],{"class":35,"line":43},[33,2957,2958],{"class":39},"\u002F\u002F    stopPropagation, target, currentTarget) plus .nativeEvent for raw access.\n",[33,2960,2961],{"class":35,"line":49},[33,2962,2963],{"class":39},"\u002F\u002F    event.target = actual click target (could be child); currentTarget = handler's element.\n",[33,2965,2966],{"class":35,"line":55},[33,2967,59],{"emptyLinePlaceholder":58},[33,2969,2970],{"class":35,"line":62},[33,2971,2972],{"class":39},"\u002F\u002F 2. React 17+ attaches event listeners to the root container (not document).\n",[33,2974,2975],{"class":35,"line":77},[33,2976,2977],{"class":39},"\u002F\u002F    Isolates multiple React trees and non-React widgets on the same page.\n",[33,2979,2980],{"class":35,"line":96},[33,2981,2982],{"class":39},"\u002F\u002F    Event pooling was removed — SyntheticEvent objects are safe to access async.\n",[33,2984,2985],{"class":35,"line":102},[33,2986,59],{"emptyLinePlaceholder":58},[33,2988,2989],{"class":35,"line":117},[33,2990,2991],{"class":39},"\u002F\u002F 3. Handler props need a FUNCTION REFERENCE, not an invocation.\n",[33,2993,2994],{"class":35,"line":131},[33,2995,2996],{"class":39},"\u002F\u002F    onClick={fn}     → correct (reference, no args)\n",[33,2998,2999],{"class":35,"line":140},[33,3000,3001],{"class":39},"\u002F\u002F    onClick={fn()}   → BUG: runs during render, assigns return value (undefined) to onClick\n",[33,3003,3004],{"class":35,"line":149},[33,3005,3006],{"class":39},"\u002F\u002F    onClick={() => fn(arg)} → correct (arrow wrapper for args)\n",[33,3008,3009],{"class":35,"line":158},[33,3010,59],{"emptyLinePlaceholder":58},[33,3012,3013],{"class":35,"line":172},[33,3014,3015],{"class":39},"\u002F\u002F 4. Missing preventDefault() on onSubmit triggers a full page reload — the\n",[33,3017,3018],{"class":35,"line":183},[33,3019,3020],{"class":39},"\u002F\u002F    #1 React beginner bug. Also required on dragover for drop to work, and\n",[33,3022,3023],{"class":35,"line":188},[33,3024,3025],{"class":39},"\u002F\u002F    on links for client-side routing.\n",[33,3027,3028],{"class":35,"line":222},[33,3029,59],{"emptyLinePlaceholder":58},[33,3031,3032],{"class":35,"line":239},[33,3033,3034],{"class":39},"\u002F\u002F 5. onChange in React fires per keystroke (not on blur like plain HTML).\n",[33,3036,3037],{"class":35,"line":245},[33,3038,3039],{"class":39},"\u002F\u002F    This underpins the controlled-input model (Chapter 14).\n",[33,3041,3042],{"class":35,"line":250},[33,3043,59],{"emptyLinePlaceholder":58},[33,3045,3046],{"class":35,"line":259},[33,3047,3048],{"class":39},"\u002F\u002F 6. stopPropagation() blocks ALL ancestor listeners, not just the one you're\n",[33,3050,3051],{"class":35,"line":278},[33,3052,3053],{"class":39},"\u002F\u002F    avoiding. Consider target === currentTarget checks for less destructive\n",[33,3055,3056],{"class":35,"line":306},[33,3057,3058],{"class":39},"\u002F\u002F    \"click outside\" detection that preserves bubbling for other listeners.\n",[3060,3061,3062],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html 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);}",{"title":28,"searchDepth":43,"depth":43,"links":3064},[3065,3069,3070,3073,3079,3081,3082,3083,3084,3085,3086],{"id":16,"depth":43,"text":17,"children":3066},[3067],{"id":352,"depth":49,"text":3068},"target vs. currentTarget — The Delegate's Trap",{"id":529,"depth":43,"text":530},{"id":648,"depth":43,"text":649,"children":3071},[3072],{"id":934,"depth":49,"text":935},{"id":1154,"depth":43,"text":3074,"children":3075},"preventDefault Patterns",[3076,3077,3078],{"id":1160,"depth":49,"text":1161},{"id":1428,"depth":49,"text":1429},{"id":1536,"depth":49,"text":1537},{"id":1685,"depth":43,"text":3080},"stopPropagation and Event Bubbling",{"id":2014,"depth":43,"text":2015},{"id":2235,"depth":43,"text":2236},{"id":2492,"depth":43,"text":2493},{"id":2614,"depth":43,"text":2615},{"id":2780,"depth":43,"text":2781},{"id":2939,"depth":43,"text":2940},"Synthetic events, event delegation architecture, handler binding patterns, preventDefault\u002FstopPropagation in production, debouncing, and the React 17 root-attachment change. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F05-event-handling",{"title":5,"description":3087},"react\u002F05-event-handling","e9bQQcZJxHMye2_8tEZefwxSrG9ZwPrQaeiCLQPDges",1789924652017]