{"id":"l-rf-3-2","courseId":"react-fundamentals","lang":"en","kind":"lesson","summary":"Attach event handlers correctly, pass arguments to them, read the event object, control bubbling and default behaviour, and pass handlers down to child components as props.","html":"<p>State changes because something happens: a click, a key press, a form submission. React’s event handling is close to the DOM’s, with a few conventions that keep components readable and a couple of traps that catch everyone once.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Pass a function, don’t call it</h2>\n<p>You attach a handler by passing a function to a prop like <code dir=\"ltr\">onClick</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> ClearButton</span><span style=\"color:#953800;--shiki-dark:#FFA657\">({ onClear }) </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> handleClick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    onClear</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">handleClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Clear watched&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The braces hold <code dir=\"ltr\">handleClick</code>, the function itself. React keeps it and calls it when the click happens. Compare:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Right: passes the function. React calls it on click.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">handleClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Clear watched&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Wrong: calls it immediately, during render.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">handleClick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Clear watched&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>The second version runs <code dir=\"ltr\">handleClick</code> every time the component renders and passes its return value (<code dir=\"ltr\">undefined</code>) as the handler. If the function sets state, you get an infinite loop and React stops it with “Too many re-renders”. When you see that error, look for parentheses inside an <code dir=\"ltr\">onSomething={…}</code>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Passing arguments</h2>\n<p>Most handlers in a list need to know which item was clicked. Wrap the call in an arrow function:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> onRemove</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movie.id)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Remove&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))}</span></span></code></pre></div>\n<p>The arrow function is created during render but only runs on click, and when it does, it calls <code dir=\"ltr\">onRemove</code> with the right id. Creating a small function per item is normal and cheap; don’t contort your code to avoid it.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Handlers as props</h2>\n<p>Components that own state pass handlers down. Children call them to report what happened. The conventions make this readable:</p>\n<ul>\n<li>Inside a component, name handlers <strong><code dir=\"ltr\">handleSomething</code></strong>: <code dir=\"ltr\">handleRemove</code>, <code dir=\"ltr\">handleSubmit</code>.</li>\n<li>Props that receive handlers are named <strong><code dir=\"ltr\">onSomething</code></strong>: <code dir=\"ltr\">onRemove</code>, <code dir=\"ltr\">onToggle</code>, matching built-in events like <code dir=\"ltr\">onClick</code>.</li>\n</ul>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">movies</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">setMovies</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useState</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">INITIAL_MOVIES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> handleRemove</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    setMovies</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> m.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> id));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieList</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onRemove</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">handleRemove</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> />;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">MovieList</code> has no idea how removal works. It only knows that when the user clicks Remove, it calls <code dir=\"ltr\">onRemove(id)</code>. The state, and the rules for changing it, stay in one place.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">The event object</h2>\n<p>React calls your handler with an event object. It wraps the browser’s native event and has the same familiar API in every browser:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> handleKeyDown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (e.key </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Escape'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    setSelectedId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onKeyDown</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">handleKeyDown</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>…&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>The properties you’ll use most are <code dir=\"ltr\">e.target</code> (the element the event started on), <code dir=\"ltr\">e.currentTarget</code> (the element whose handler is running), <code dir=\"ltr\">e.key</code> for keyboard events, and two methods: <code dir=\"ltr\">e.preventDefault()</code> and <code dir=\"ltr\">e.stopPropagation()</code>.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Bubbling and default behaviour</h2>\n<p>Events <strong>bubble</strong>: a click on a button inside a card triggers the button’s handler, then the card’s, then any ancestor’s. That’s why the <code dir=\"ltr\">onKeyDown</code> on <code dir=\"ltr\">&lt;main&gt;</code> above catches Escape pressed while any button inside it has focus. Usually bubbling is what you want, but not always:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> onOpen</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movie.id)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      e.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">stopPropagation</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">      onRemove</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movie.id);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    Remove</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>Without <code dir=\"ltr\">stopPropagation</code>, clicking Remove would also open the movie’s details. (A whole-card click target is a mouse convenience; keep a real button or link for opening the details too, so keyboard users can reach it.) Keep this pattern for the cases that need it; reaching for it everywhere makes event flow hard to follow.</p>\n<p><code dir=\"ltr\">preventDefault</code> is different: it stops the <strong>browser’s</strong> built-in behaviour, such as a link navigating or a form reloading the page. It doesn’t stop bubbling. You’ll use it on the Watchlist’s form in the next lesson.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Clickable divs</span></p>\n<p><code dir=\"ltr\">&lt;div onClick={select}&gt;Arrival&lt;/div&gt;</code> works with a mouse and nothing else. It can’t receive keyboard focus, Enter and Space do nothing, and screen readers don’t announce it as interactive. Use <code dir=\"ltr\">&lt;button type=&quot;button&quot;&gt;</code> for actions and <code dir=\"ltr\">&lt;a href&gt;</code> for navigation. You can style a button to look like anything.</p>\n</aside>\n<h2 id=\"sec-6\" tabindex=\"-1\">Handlers are where side effects belong</h2>\n<p>Components must be pure while rendering, but event handlers don’t run during rendering. They run because the user did something, so they’re the right place for side effects: setting state, writing to localStorage, sending a request, logging analytics. When you’re unsure where some code should live, ask “what caused it?” If the answer is a specific user action, it goes in that action’s handler.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Read the error, find the parentheses</span></p>\n<p>“Too many re-renders” nearly always means a setter is called during render, usually through <code dir=\"ltr\">onClick={doSomething()}</code>. A handler that quietly does nothing, or logs an event object where you expected an id, was usually passed directly (<code dir=\"ltr\">onClick={onRemove}</code>) instead of wrapped in an arrow function. Both come from how the function was passed.</p>\n</aside>\n<p>Next, you’ll handle the most important event in the Watchlist, the form submission that adds a movie, and learn how controlled inputs keep form fields and state in sync.</p>\n","toc":[{"id":"sec-1","text":"Pass a function, don’t call it","level":2},{"id":"sec-2","text":"Passing arguments","level":2},{"id":"sec-3","text":"Handlers as props","level":2},{"id":"sec-4","text":"The event object","level":2},{"id":"sec-5","text":"Bubbling and default behaviour","level":2},{"id":"sec-6","text":"Handlers are where side effects belong","level":2}],"takeaways":["Pass a function to <code dir=\"ltr\">onClick</code>, as in <code dir=\"ltr\">onClick={handleClick}</code>; writing <code dir=\"ltr\">handleClick()</code> calls it during render.","To pass an argument, wrap the call in an arrow function, as in <code dir=\"ltr\">onClick={() =&gt; onRemove(movie.id)}</code>.","Handlers receive an event object with <code dir=\"ltr\">target</code>, <code dir=\"ltr\">key</code>, <code dir=\"ltr\">preventDefault()</code> and <code dir=\"ltr\">stopPropagation()</code>.","Name handlers <code dir=\"ltr\">handleX</code> inside a component and callback props <code dir=\"ltr\">onX</code>, so the data flow reads clearly.","Use real <code dir=\"ltr\">&lt;button&gt;</code> elements for clickable things so keyboard and screen reader users can use them too."],"further":[{"title":"Responding to Events","url":"https://react.dev/learn/responding-to-events"},{"title":"Common components (React event object)","url":"https://react.dev/reference/react-dom/components/common#react-event-object"},{"title":"Event bubbling (MDN)","url":"https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling"}],"quiz":[{"q":"<p><code dir=\"ltr\">&lt;button onClick={setSelectedId(movie.id)}&gt;Select&lt;/button&gt;</code> crashes the app with “Too many re-renders”. Why?</p>","options":[{"text":"The setter is called while rendering, which triggers another render, which calls it again, forever.","why":"Correct. The braces contain a call, so it runs during render. Write <code dir=\"ltr\">onClick={() =&gt; setSelectedId(movie.id)}</code> to pass a function instead."},{"text":"<code dir=\"ltr\">onClick</code> only accepts functions declared with the <code dir=\"ltr\">function</code> keyword.","why":"Any function works, including arrow functions. The problem is that this isn’t a function at all; it’s the result of a call."},{"text":"Buttons can’t change state; only forms can.","why":"Any event handler can set state. A button click is the most common way to do it."}],"answer":0},{"q":"<p><code dir=\"ltr\">MovieItem</code> receives <code dir=\"ltr\">onRemove</code> from its parent. The parent’s <code dir=\"ltr\">removeMovie(id)</code> expects a movie id. Which is correct inside MovieItem?</p>","options":[{"text":"<code dir=\"ltr\">&lt;button onClick={onRemove}&gt;Remove&lt;/button&gt;</code>","why":"React calls <code dir=\"ltr\">onRemove</code> with the click event, so the parent receives an event object instead of an id."},{"text":"<code dir=\"ltr\">&lt;button onClick={onRemove(movie.id)}&gt;Remove&lt;/button&gt;</code>","why":"That calls <code dir=\"ltr\">onRemove</code> during render and removes the movie immediately, before anyone clicks."},{"text":"<code dir=\"ltr\">&lt;button onClick={() =&gt; onRemove(movie.id)}&gt;Remove&lt;/button&gt;</code>","why":"Correct. The arrow function runs on click and calls <code dir=\"ltr\">onRemove</code> with exactly the argument the parent expects."},{"text":"<code dir=\"ltr\">&lt;button onRemove={movie.id}&gt;Remove&lt;/button&gt;</code>","why":"DOM elements don’t know about <code dir=\"ltr\">onRemove</code>. Only real events like <code dir=\"ltr\">onClick</code> are wired up."}],"answer":2},{"q":"<p>A movie card has <code dir=\"ltr\">onClick={openDetails}</code>, and inside it a Remove button with <code dir=\"ltr\">onClick={() =&gt; onRemove(movie.id)}</code>. Clicking Remove also opens the details. What’s the fix?</p>","options":[{"text":"Call <code dir=\"ltr\">e.preventDefault()</code> in the Remove handler.","why":"preventDefault stops the browser’s default action, such as a form submitting. It doesn’t stop the click bubbling up to the card."},{"text":"Call <code dir=\"ltr\">e.stopPropagation()</code> in the Remove handler before calling <code dir=\"ltr\">onRemove</code>.","why":"Correct. The click bubbles from the button to the card. stopPropagation stops it at the button."},{"text":"Move the Remove button outside of the component.","why":"That works visually only if the layout allows it, and it avoids the problem instead of understanding it."},{"text":"Use <code dir=\"ltr\">onMouseDown</code> on the Remove button instead.","why":"The card’s click handler still fires on click, and mouse-only events break keyboard use."}],"answer":1},{"q":"<p>Why should a clickable movie title be a <code dir=\"ltr\">&lt;button&gt;</code> instead of a <code dir=\"ltr\">&lt;div onClick={…}&gt;</code>?</p>","options":[{"text":"A div’s onClick only works in React 18 and earlier.","why":"onClick works on any element in every React version. The issue is what the element offers users."},{"text":"Buttons render faster than divs.","why":"Rendering speed is the same. The difference is built-in behaviour and accessibility."},{"text":"React warns about click handlers on divs and refuses to run them.","why":"React doesn’t refuse; the click works with a mouse. Keyboard and assistive technology users are the ones left out."},{"text":"A button is focusable, works with Enter and Space, and is announced as a button by screen readers; a div does none of that.","why":"Correct. You’d have to add a role, a tabIndex and key handlers to a div to get what a button gives you for free."}],"answer":3}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival' },\n  { id: 'p2', title: 'Past Lives' },\n  { id: 'd3', title: 'Dune: Part Two' },\n];\n\nfunction MovieItem({ movie, onSelect, onRemove }) {\n  return (\n    <li>\n      <button className=\"title\" onClick={() => onSelect(movie.id)}>\n        {movie.title}\n      </button>\n      <button className=\"remove\" onClick={onRemove}>\n        Remove\n      </button>\n    </li>\n  );\n}\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n  const [selectedId, setSelectedId] = useState(null);\n  const selected = movies.find((m) => m.id === selectedId);\n\n  function handleRemove(id) {\n    setMovies(movies.filter((m) => m.id !== id));\n  }\n\n  // Clear the selection when Escape is pressed anywhere inside <main>\n\n  return (\n    <main>\n      <p id=\"selected\">Selected: {selected ? selected.title : 'none'}</p>\n      <ul>\n        {movies.map((movie) => (\n          <MovieItem\n            key={movie.id}\n            movie={movie}\n            onSelect={setSelectedId}\n            onRemove={handleRemove}\n          />\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"solution":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival' },\n  { id: 'p2', title: 'Past Lives' },\n  { id: 'd3', title: 'Dune: Part Two' },\n];\n\nfunction MovieItem({ movie, onSelect, onRemove }) {\n  return (\n    <li>\n      <button className=\"title\" onClick={() => onSelect(movie.id)}>\n        {movie.title}\n      </button>\n      <button className=\"remove\" onClick={() => onRemove(movie.id)}>\n        Remove\n      </button>\n    </li>\n  );\n}\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n  const [selectedId, setSelectedId] = useState(null);\n  const selected = movies.find((m) => m.id === selectedId);\n\n  function handleRemove(id) {\n    setMovies(movies.filter((m) => m.id !== id));\n  }\n\n  function handleKeyDown(e) {\n    if (e.key === 'Escape') {\n      setSelectedId(null);\n    }\n  }\n\n  return (\n    <main onKeyDown={handleKeyDown}>\n      <p id=\"selected\">Selected: {selected ? selected.title : 'none'}</p>\n      <ul>\n        {movies.map((movie) => (\n          <MovieItem\n            key={movie.id}\n            movie={movie}\n            onSelect={setSelectedId}\n            onRemove={handleRemove}\n          />\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"checks":[{"id":"select","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('.title')) === t); await click(row('Past Lives').querySelector('.title')); return text('#selected') === 'Selected: Past Lives';"},{"id":"remove","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('.title')) === t); await click(row('Arrival').querySelector('.remove')); return $$('li').length === 2 && !row('Arrival') && text('#selected') === 'Selected: Past Lives';"},{"id":"remove-selected","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('.title')) === t); await click(row('Past Lives').querySelector('.remove')); return $$('li').length === 1 && text('#selected') === 'Selected: none';"},{"id":"escape","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('.title')) === t); await click(row('Dune: Part Two').querySelector('.title')); if (text('#selected') !== 'Selected: Dune: Part Two') return false; await press(row('Dune: Part Two').querySelector('.title'), 'Escape'); return text('#selected') === 'Selected: none';"}],"title":"Select, remove and Escape","prompt":"<p>Clicking a movie’s title selects it. Two things are missing:</p>\n<ol>\n<li><strong>Remove</strong> doesn’t remove anything. <code dir=\"ltr\">App</code>’s <code dir=\"ltr\">handleRemove(id)</code> expects a movie id; fix how <code dir=\"ltr\">MovieItem</code> calls <code dir=\"ltr\">onRemove</code>.</li>\n<li>Pressing <strong>Escape</strong> while focus is anywhere inside <code dir=\"ltr\">&lt;main&gt;</code> should clear the selection, so <code dir=\"ltr\">#selected</code> reads <strong>Selected: none</strong>.</li>\n</ol>\n<p>When the selected movie is removed, the selection should also show <strong>none</strong>. (Look at how <code dir=\"ltr\">selected</code> is calculated before adding any code for this.)</p>\n","hints":["Right now <code dir=\"ltr\">onClick={onRemove}</code> means React calls <code dir=\"ltr\">onRemove(event)</code>. Wrap it in an arrow function to pass <code dir=\"ltr\">movie.id</code>.","Keyboard events bubble. One <code dir=\"ltr\">onKeyDown</code> handler on <code dir=\"ltr\">&lt;main&gt;</code> sees Escape pressed on any button inside it.","In the handler, check <code dir=\"ltr\">e.key === 'Escape'</code> and call <code dir=\"ltr\">setSelectedId(null)</code>."],"explanation":"<p><code dir=\"ltr\">onClick={onRemove}</code> hands React a function that it calls with the click event, so <code dir=\"ltr\">handleRemove</code> received an event instead of an id and filtered nothing out. <code dir=\"ltr\">() =&gt; onRemove(movie.id)</code> passes exactly the argument the parent expects. Escape works from one handler on <code dir=\"ltr\">&lt;main&gt;</code> because keydown events bubble up from the focused button. Removing the selected movie needs no extra code: <code dir=\"ltr\">selected</code> is derived with <code dir=\"ltr\">find</code>, so once the movie is gone it’s <code dir=\"ltr\">undefined</code> and the text falls back to “none”.</p>\n","checkLabels":{"select":"Clicking “Past Lives” selects it","remove":"Remove deletes Arrival and keeps the selection","remove-selected":"Removing the selected movie resets the selection to none","escape":"Escape clears the selection"}},"words":676,"readMinutes":3}