{"id":"l-rf-3-4","courseId":"react-fundamentals","lang":"en","kind":"lesson","summary":"Share state between sibling components by moving it to their closest common parent, passing values down and change handlers up, so the app keeps a single source of truth.","html":"<p>You now have an add-movie form, a movie list and filter buttons. Each works alone. Put them on one screen and they don’t talk to each other: the form can’t add to a list that lives in another component, and the list doesn’t know which filter button is pressed. Components can’t reach into each other’s state. That’s deliberate, and the fix is a pattern you’ll use in every React app you build.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The problem: siblings can’t share</h2>\n<p>Here’s the filter bar as you might first write it:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> FilterBar</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\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">setFilter</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:#0A3069;--shiki-dark:#A5D6FF\">'all'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</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\">div</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filter </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'all'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> setFilter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'all'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>All&#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\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filter </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'to-watch'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> setFilter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'to-watch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>To watch&#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\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filter </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> setFilter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'watched'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>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\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</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></code></pre></div>\n<p>The buttons highlight correctly, but <code dir=\"ltr\">MovieList</code> is a sibling. It has no access to <code dir=\"ltr\">filter</code>, so it shows everything no matter what you click. State is private to the component that declares it.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">The fix: lift it to the common parent</h2>\n<p>Find the closest component that’s a parent of everything that needs the state. For the filter, that’s <code dir=\"ltr\">App</code>, the parent of both <code dir=\"ltr\">FilterBar</code> and <code dir=\"ltr\">MovieList</code>. Move the state there, then:</p>\n<ol>\n<li>pass the <strong>value</strong> down to everyone who reads it, and</li>\n<li>pass a <strong>function</strong> down to everyone who changes it.</li>\n</ol>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"1\"><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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">setFilter</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:#0A3069;--shiki-dark:#A5D6FF\">'all'</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\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> visible</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    filter </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'all'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> filter </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> m.watched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">m.watched,</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\">  function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> handleAdd</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">title</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\">([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movies, { id: crypto.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">randomUUID</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), title, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</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\"> (</span></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:#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\">Header</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:#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\">AddMovieForm</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onAdd</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">handleAdd</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\">FilterBar</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filter</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onChange</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">setFilter</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\">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\">visible</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\">main</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:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> FilterBar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">onChange</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</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\">div</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> role</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"group\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-label</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Filter movies\"</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'all'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> onChange</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'all'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>All&#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\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'to-watch'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> onChange</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'to-watch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>To watch&#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\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\"> onChange</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'watched'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>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\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">div</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></code></pre></div>\n<p><code dir=\"ltr\">FilterBar</code> no longer has state. It displays <code dir=\"ltr\">value</code> and reports clicks through <code dir=\"ltr\">onChange</code>. A component like this is called <strong>controlled</strong>: its parent decides what it shows. It’s now also easier to test and reuse, because everything it does is visible in its props.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 280\" role=\"img\" aria-labelledby=\"l-rf-3-4-t1\"><defs><marker id=\"arrow-l-rf-3-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-rf-3-4-t1\">App owns movies and filter state. It passes movies to Header, onAdd to AddMovieForm, value and onChange to FilterBar, and the visible movies to MovieList. AddMovieForm and FilterBar call their callbacks to send changes back up to App.</title>\n  <rect class=\"d-box-primary\" x=\"230\" y=\"16\" width=\"240\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"42\" text-anchor=\"middle\">App</text>\n  <text class=\"d-code\" x=\"350\" y=\"66\" text-anchor=\"middle\">movies · filter</text>\n  <rect class=\"d-box\" x=\"10\" y=\"190\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"220\" text-anchor=\"middle\">Header</text>\n  <rect class=\"d-box\" x=\"180\" y=\"190\" width=\"160\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"260\" y=\"220\" text-anchor=\"middle\">AddMovieForm</text>\n  <rect class=\"d-box\" x=\"360\" y=\"190\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"435\" y=\"220\" text-anchor=\"middle\">FilterBar</text>\n  <rect class=\"d-box-accent\" x=\"530\" y=\"190\" width=\"160\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"610\" y=\"220\" text-anchor=\"middle\">MovieList</text>\n  <path class=\"d-arrow\" d=\"M280 80 L95 188\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 80 L605 188\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M330 80 L285 188\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M370 80 L420 188\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M245 190 L300 84\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M455 190 L400 84\" marker-end=\"url(#arrow-l-rf-3-4-1)\"/>\n  <text class=\"d-label-muted\" x=\"140\" y=\"130\" text-anchor=\"middle\">props down</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"268\" text-anchor=\"middle\">dashed: onAdd / onChange calls back up</text>\n</svg><figcaption>State lives in App; props flow down, events flow up</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Data down, events up</h2>\n<p>Notice the shape. Data flows <strong>down</strong> the tree as props: <code dir=\"ltr\">movies</code>, <code dir=\"ltr\">filter</code>, <code dir=\"ltr\">visible</code>. Changes flow <strong>up</strong> as function calls: the form calls <code dir=\"ltr\">onAdd</code>, the filter bar calls <code dir=\"ltr\">onChange</code>. Only <code dir=\"ltr\">App</code> ever calls <code dir=\"ltr\">setMovies</code> or <code dir=\"ltr\">setFilter</code>.</p>\n<p>That’s the payoff. If the list ever shows the wrong movies, there’s exactly one place that decides what’s in it. You follow the props up to their owner and read the code that changes them. Compare that with an app where any component can poke any other: a bug could come from anywhere.</p>\n<p>Look at what’s not state, too. <code dir=\"ltr\">visible</code> is calculated from <code dir=\"ltr\">movies</code> and <code dir=\"ltr\">filter</code> on every render, and <code dir=\"ltr\">Header</code> derives its count from <code dir=\"ltr\">movies</code>. Lifting state doesn’t mean lifting everything; it means one owner for each piece of real state, and everything else derived from it.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Signs you need to lift</h2>\n<p>You rarely plan every piece of state perfectly up front. You discover the need to lift while building, and the signs are consistent:</p>\n<ul>\n<li>Two components show values that should match but sometimes don’t.</li>\n<li>You’re about to write an effect whose only job is to copy one component’s state into another’s.</li>\n<li>A child needs to change something a sibling displays.</li>\n</ul>\n<p>Each of these means the same data has two owners, or the owner is too low in the tree. Lifting is a small, mechanical refactor: cut the <code dir=\"ltr\">useState</code> line, paste it into the parent, and replace the old state in the child with props.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">As low as possible, as high as necessary</h2>\n<p>Lifting has a cost: the owner re-renders when the state changes, along with its children, and props have to travel further. So don’t lift everything to <code dir=\"ltr\">App</code> by reflex.</p>\n<p>The text being typed into the title field is a good example. Only <code dir=\"ltr\">AddMovieForm</code> cares about it while you type. <code dir=\"ltr\">App</code> only needs the finished title, which it gets from <code dir=\"ltr\">onAdd</code>. So the draft stays in the form. If later some other component needed to preview the draft, you’d lift it then.</p>\n<p>When state needs to be read by components far apart in the tree, passing it through every level becomes tedious. You saw one fix in the composition lesson (pass elements down), and React’s <strong>context</strong> is the other. For the Watchlist, a few props are all you need.</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>Copying props into state</span></p>\n<p><code dir=\"ltr\">const [title, setTitle] = useState(props.title)</code> looks like a way to “receive” data, but <code dir=\"ltr\">useState</code> only reads its argument on the first render. When the parent passes a new title, the child keeps the old copy. If the component only displays the value, use the prop directly. If it needs an editable draft that starts from the prop, name it clearly (<code dir=\"ltr\">initialTitle</code>) so everyone knows later changes are ignored.</p>\n</aside>\n<p>Your Watchlist now has one source of truth and three components that read and change it. Next, you’ll sync that state with something outside React, the browser tab’s title and localStorage, using effects.</p>\n","toc":[{"id":"sec-1","text":"The problem: siblings can’t share","level":2},{"id":"sec-2","text":"The fix: lift it to the common parent","level":2},{"id":"sec-3","text":"Data down, events up","level":2},{"id":"sec-4","text":"Signs you need to lift","level":2},{"id":"sec-5","text":"As low as possible, as high as necessary","level":2}],"takeaways":["When two components need the same changing data, move the state to their closest common parent and pass it down as props.","Children change shared state by calling handler props such as <code dir=\"ltr\">onAdd</code> or <code dir=\"ltr\">onFilterChange</code>; data flows down and events flow up.","Keep each piece of state as low in the tree as possible but as high as necessary.","Don’t copy a prop into state with <code dir=\"ltr\">useState(prop)</code>; the copy ignores later changes. Use the prop directly or derive from it."],"further":[{"title":"Sharing State Between Components","url":"https://react.dev/learn/sharing-state-between-components"},{"title":"Choosing the State Structure","url":"https://react.dev/learn/choosing-the-state-structure"}],"quiz":[{"q":"<p><code dir=\"ltr\">FilterBar</code> keeps <code dir=\"ltr\">const [filter, setFilter] = useState('all')</code>. <code dir=\"ltr\">MovieList</code>, its sibling, needs to show only the movies matching the filter. What’s the fix?</p>","options":[{"text":"Have FilterBar write the filter to a global variable that MovieList reads.","why":"Changing a module variable doesn’t re-render anything, so MovieList would show stale results."},{"text":"Give MovieList its own copy of the filter state and keep both in sync with an effect.","why":"Two copies of the same state that must be synced is exactly the problem lifting state solves. It’s more code and more bugs."},{"text":"Move the filter state into their common parent, pass <code dir=\"ltr\">filter</code> to both, and pass <code dir=\"ltr\">setFilter</code> (or a handler) to FilterBar.","why":"Correct. One owner, one source of truth. FilterBar shows and changes the value; MovieList reads it."},{"text":"Make MovieList a child of FilterBar so it can read the state.","why":"Rearranging the UI tree to fit data is backwards, and the header that counts movies would still be left out."}],"answer":2},{"q":"<p>Which piece of Watchlist state should stay inside <code dir=\"ltr\">AddMovieForm</code> rather than being lifted to <code dir=\"ltr\">App</code>?</p>","options":[{"text":"The text currently typed in the title field.","why":"Correct. Only the form uses the draft text. App only needs the finished movie, which it gets through <code dir=\"ltr\">onAdd</code>."},{"text":"The list of movies.","why":"The header, the list and the form all depend on the movies, so they belong in App."},{"text":"The current filter.","why":"Both the filter buttons and the list need it, so it lives in their common parent."}],"answer":0},{"q":"<p><code dir=\"ltr\">MovieTitle</code> shows a title it receives as a prop. When the parent renames the movie, the child keeps showing the old title. Why?</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> MovieTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">title</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\">text</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\">(title);</span></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\">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\">text</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\">}</span></span></code></pre></div>\n","options":[{"text":"Props can’t contain strings that change.","why":"Props can change on every render. This component ignores the change."},{"text":"The parent forgot to use a key.","why":"A key change would remount it and hide the bug, but the bug is the copied state, not the parent."},{"text":"The h2 needs an <code dir=\"ltr\">onChange</code> handler.","why":"Headings don’t have change events. The issue is where the text comes from."},{"text":"<code dir=\"ltr\">useState</code> only uses its argument on the first render, so <code dir=\"ltr\">text</code> is a frozen copy. Render <code dir=\"ltr\">{title}</code> directly.","why":"Correct. The initial value is read once. If the component doesn’t need to edit the value, it shouldn’t copy it into state."}],"answer":3},{"q":"<p>In the Watchlist, data flows down as props and changes flow up through callbacks. What’s the main benefit of this one-way flow?</p>","options":[{"text":"It makes React render faster than two-way binding.","why":"Performance isn’t the point; the benefit is about understanding and debugging your app."},{"text":"To find out why something shows a value, you follow props up to the one component that owns the state.","why":"Correct. Every piece of state has one owner, and only that owner changes it, so bugs have one place to look."},{"text":"It removes the need for event handlers.","why":"Callbacks are event handlers passed down. One-way flow depends on them."}],"answer":1}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"import { useState } from 'react';\n\nconst MOVIES = [\n  { id: 'a1', title: 'Arrival', watched: true },\n  { id: 'p2', title: 'Past Lives', watched: false },\n  { id: 'd3', title: 'Dune: Part Two', watched: false },\n];\n\nfunction MovieList({ movies }) {\n  return (\n    <ul>\n      {movies.map((m) => <li key={m.id}>{m.title}</li>)}\n    </ul>\n  );\n}\n\nfunction FilterBar() {\n  const [filter, setFilter] = useState('all');\n  return (\n    <div>\n      <button aria-pressed={filter === 'all'} onClick={() => setFilter('all')}>All</button>\n      <button aria-pressed={filter === 'to-watch'} onClick={() => setFilter('to-watch')}>To watch</button>\n      <button aria-pressed={filter === 'watched'} onClick={() => setFilter('watched')}>Watched</button>\n    </div>\n  );\n}\n\nexport default function App() {\n  // MovieList should only show movies that match the selected filter.\n  return (\n    <main>\n      <FilterBar />\n      <MovieList movies={MOVIES} />\n    </main>\n  );\n}\n"},"solution":{"jsx":"import { useState } from 'react';\n\nconst MOVIES = [\n  { id: 'a1', title: 'Arrival', watched: true },\n  { id: 'p2', title: 'Past Lives', watched: false },\n  { id: 'd3', title: 'Dune: Part Two', watched: false },\n];\n\nfunction MovieList({ movies }) {\n  return (\n    <ul>\n      {movies.map((m) => <li key={m.id}>{m.title}</li>)}\n    </ul>\n  );\n}\n\nfunction FilterBar({ value, onChange }) {\n  return (\n    <div>\n      <button aria-pressed={value === 'all'} onClick={() => onChange('all')}>All</button>\n      <button aria-pressed={value === 'to-watch'} onClick={() => onChange('to-watch')}>To watch</button>\n      <button aria-pressed={value === 'watched'} onClick={() => onChange('watched')}>Watched</button>\n    </div>\n  );\n}\n\nexport default function App() {\n  const [filter, setFilter] = useState('all');\n  const visible = MOVIES.filter((m) =>\n    filter === 'all' ? true : filter === 'watched' ? m.watched : !m.watched,\n  );\n\n  return (\n    <main>\n      <FilterBar value={filter} onChange={setFilter} />\n      <MovieList movies={visible} />\n    </main>\n  );\n}\n"},"checks":[{"id":"starts-all","test":"const btn = (t) => $$('button').find((b) => text(b) === t); const pressed = (t) => btn(t).getAttribute('aria-pressed') === 'true'; const titles = () => $$('li').map((li) => text(li)).join('|'); return titles() === 'Arrival|Past Lives|Dune: Part Two' && pressed('All') && !pressed('To watch');"},{"id":"to-watch","test":"const btn = (t) => $$('button').find((b) => text(b) === t); const pressed = (t) => btn(t).getAttribute('aria-pressed') === 'true'; const titles = () => $$('li').map((li) => text(li)).join('|'); await click(btn('To watch')); return titles() === 'Past Lives|Dune: Part Two' && pressed('To watch') && !pressed('All');"},{"id":"watched","test":"const btn = (t) => $$('button').find((b) => text(b) === t); const pressed = (t) => btn(t).getAttribute('aria-pressed') === 'true'; const titles = () => $$('li').map((li) => text(li)).join('|'); await click(btn('Watched')); return titles() === 'Arrival' && pressed('Watched') && !pressed('To watch');"},{"id":"back-to-all","test":"const btn = (t) => $$('button').find((b) => text(b) === t); const pressed = (t) => btn(t).getAttribute('aria-pressed') === 'true'; const titles = () => $$('li').map((li) => text(li)).join('|'); await click(btn('All')); return $$('li').length === 3 && pressed('All');"}],"title":"Lift the filter","prompt":"<p>The filter buttons highlight correctly, but the list never changes, because <code dir=\"ltr\">FilterBar</code> keeps the filter to itself.</p>\n<p>Lift the filter state into <code dir=\"ltr\">App</code>. Pass the current value and a way to change it down to <code dir=\"ltr\">FilterBar</code> (it should have no state of its own), and give <code dir=\"ltr\">MovieList</code> only the movies that match:</p>\n<ul>\n<li><strong>All</strong>: every movie</li>\n<li><strong>To watch</strong>: <code dir=\"ltr\">watched</code> is <code dir=\"ltr\">false</code></li>\n<li><strong>Watched</strong>: <code dir=\"ltr\">watched</code> is <code dir=\"ltr\">true</code></li>\n</ul>\n<p>Keep <code dir=\"ltr\">aria-pressed</code> on the buttons working.</p>\n","hints":["Move <code dir=\"ltr\">const [filter, setFilter] = useState('all')</code> from FilterBar into App.","Give FilterBar two props, for example <code dir=\"ltr\">value</code> and <code dir=\"ltr\">onChange</code>, and use them in place of its old state.","In App, derive the list: <code dir=\"ltr\">MOVIES.filter((m) =&gt; filter === 'all' ? true : filter === 'watched' ? m.watched : !m.watched)</code>."],"explanation":"<p>State is private to the component that declares it, so a sibling can never read it. Moving <code dir=\"ltr\">filter</code> to <code dir=\"ltr\">App</code>, the closest common parent, gives it one owner. <code dir=\"ltr\">FilterBar</code> becomes a controlled component that shows <code dir=\"ltr\">value</code> and reports clicks through <code dir=\"ltr\">onChange</code>, and <code dir=\"ltr\">App</code> derives the visible movies from <code dir=\"ltr\">MOVIES</code> and <code dir=\"ltr\">filter</code> on every render instead of storing a filtered copy.</p>\n","checkLabels":{"starts-all":"Starts on All, showing all three movies","to-watch":"To watch shows Past Lives and Dune: Part Two, and is pressed","watched":"Watched shows only Arrival, and is pressed","back-to-all":"All shows every movie again"}},"words":721,"readMinutes":3}