{"id":"l-rf-3-4","courseId":"react-fundamentals","lang":"ar","kind":"lesson","summary":"شارك الـ state بين components إخوة بنقله إلى أقرب أب مشترك لها، وتمرير القيم إلى الأسفل ومعالجات التغيير إلى الأعلى، ليبقى للتطبيق مصدر واحد للحقيقة.","html":"<p>لديك الآن نموذج لإضافة الأفلام، وقائمة أفلام، وأزرار فلترة. كل منها يعمل وحده. ضعها على شاشة واحدة ولن يتحدّث بعضها مع بعض: لا يستطيع النموذج الإضافة إلى قائمة تعيش في component آخر، ولا تعرف القائمة أي زر فلترة مضغوط. لا تستطيع الـ components أن تمدّ يدها إلى الـ state الخاص ببعضها. هذا مقصود، والحلّ نمط ستستخدمه في كل تطبيق React تبنيه.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">المشكلة: الإخوة لا يتشاركون</h2>\n<p>هذا شريط الفلترة كما قد تكتبه أول مرة:</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=\"نسخ الكود\"><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>نسخ</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>يظهر الزر النشط مميّزًا بشكل صحيح، لكن <code dir=\"ltr\">MovieList</code> أخ له. لا يصل إلى <code dir=\"ltr\">filter</code>، فيعرض كل شيء مهما نقرت. الـ state خاص بالـ component الذي يعلن عنه.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الحلّ: ارفعه إلى الأب المشترك</h2>\n<p>اعثر على أقرب component يكون أبًا لكل ما يحتاج إلى الـ state. بالنسبة إلى الفلتر، هو <code dir=\"ltr\">App</code>، أب كلٍّ من <code dir=\"ltr\">FilterBar</code> و<code dir=\"ltr\">MovieList</code>. انقل الـ state إليه، ثم:</p>\n<ol>\n<li>مرّر <strong>القيمة</strong> إلى كل من يقرؤها، و</li>\n<li>مرّر <strong>دالة</strong> إلى كل من يغيّرها.</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=\"نسخ الكود\"><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>نسخ</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> أي state. إنه يعرض <code dir=\"ltr\">value</code> ويبلّغ عن النقرات عبر <code dir=\"ltr\">onChange</code>. ويُسمّى component كهذا <strong>controlled</strong> (متحكَّمًا به): أبوه يقرّر ما يعرضه. وصار الآن أسهل في الاختبار وإعادة الاستخدام، لأن كل ما يفعله ظاهر في الـ 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 الـ state المسمّى movies والمسمّى filter. يمرّر movies إلى Header، وonAdd إلى AddMovieForm، وvalue وonChange إلى FilterBar، والأفلام الظاهرة إلى MovieList. ويستدعي AddMovieForm وFilterBar الـ callbacks الخاصة بهما لإرسال التغييرات صعودًا إلى 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 تنزل</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"268\" text-anchor=\"middle\">المتقطّع: استدعاءات onAdd / onChange تصعد</text>\n</svg><figcaption>يعيش الـ state في App؛ الـ props تنزل والأحداث تصعد</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">البيانات تنزل، والأحداث تصعد</h2>\n<p>لاحظ الشكل. البيانات تتدفّق <strong>نزولًا</strong> في الشجرة كـ props: <code dir=\"ltr\">movies</code> و<code dir=\"ltr\">filter</code> و<code dir=\"ltr\">visible</code>. والتغييرات تتدفّق <strong>صعودًا</strong> كاستدعاءات دوال: النموذج يستدعي <code dir=\"ltr\">onAdd</code>، وشريط الفلترة يستدعي <code dir=\"ltr\">onChange</code>. ولا يستدعي <code dir=\"ltr\">setMovies</code> أو <code dir=\"ltr\">setFilter</code> إلا <code dir=\"ltr\">App</code>.</p>\n<p>هذه هي الثمرة. إذا عرضت القائمة يومًا أفلامًا خاطئة، فهناك مكان واحد بالضبط يقرّر ما فيها. تتبع الـ props صعودًا إلى مالكها وتقرأ الكود الذي يغيّرها. قارن ذلك بتطبيق يستطيع فيه أي component أن يعبث بأي component آخر: قد يأتي الخطأ من أي مكان.</p>\n<p>وانظر أيضًا إلى ما ليس state. تُحسب <code dir=\"ltr\">visible</code> من <code dir=\"ltr\">movies</code> و<code dir=\"ltr\">filter</code> في كل render، ويشتقّ <code dir=\"ltr\">Header</code> العدد من <code dir=\"ltr\">movies</code>. رفع الـ state لا يعني رفع كل شيء؛ بل يعني مالكًا واحدًا لكل قطعة state حقيقية، وكل ما عداها مشتقّ منها.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">علامات أنك تحتاج إلى الرفع</h2>\n<p>نادرًا ما تخطّط لكل قطعة state بشكل مثالي من البداية. تكتشف الحاجة إلى الرفع أثناء البناء، والعلامات ثابتة:</p>\n<ul>\n<li>componentان يعرضان قيمتين يجب أن تتطابقا لكنهما لا تتطابقان أحيانًا.</li>\n<li>أنت على وشك كتابة effect وظيفته الوحيدة نسخ state component إلى آخر.</li>\n<li>ابنٌ يحتاج إلى تغيير شيء يعرضه أخوه.</li>\n</ul>\n<p>كل واحدة من هذه تعني أن للبيانات نفسها مالكين، أو أن المالك منخفض جدًا في الشجرة. والرفع إعادة هيكلة صغيرة وآلية: اقطع سطر <code dir=\"ltr\">useState</code>، والصقه في الأب، واستبدل الـ state القديم في الابن بـ props.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">في أدنى مكان ممكن، وأعلى مكان ضروري</h2>\n<p>للرفع ثمن: المالك يعيد الـ render حين يتغيّر الـ state، ومعه أبناؤه، والـ props تضطر إلى قطع مسافة أطول. فلا ترفع كل شيء إلى <code dir=\"ltr\">App</code> بحكم العادة.</p>\n<p>النص الذي يُكتب في حقل العنوان مثال جيد. لا يهتمّ به إلا <code dir=\"ltr\">AddMovieForm</code> أثناء الكتابة. أمّا <code dir=\"ltr\">App</code> فلا يحتاج إلا إلى العنوان النهائي، الذي يصله من <code dir=\"ltr\">onAdd</code>. لذا تبقى المسوّدة في النموذج. وإذا احتاج component آخر لاحقًا إلى معاينة المسوّدة، فسترفعها حينها.</p>\n<p>وحين يحتاج إلى الـ state components متباعدة في الشجرة، يصبح تمريره عبر كل مستوى مملًّا. رأيت حلًّا في درس التركيب (تمرير العناصر إلى الأسفل)، والـ <strong>context</strong> في React هو الحل الآخر. أمّا Watchlist فتكفيه بضعة props.</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\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسخ الـ props إلى الـ state</span></p>\n<p>يبدو <code dir=\"ltr\">const [title, setTitle] = useState(props.title)</code> طريقةً «لاستقبال» البيانات، لكن <code dir=\"ltr\">useState</code> لا تقرأ وسيطها إلا في الـ render الأول. وحين يمرّر الأب عنوانًا جديدًا، يحتفظ الابن بالنسخة القديمة. إذا كان الـ component يكتفي بعرض القيمة، فاستخدم الـ prop مباشرة. وإذا كان يحتاج إلى مسوّدة قابلة للتعديل تبدأ من الـ prop، فسمِّه باسم واضح (<code dir=\"ltr\">initialTitle</code>) ليعرف الجميع أن التغييرات اللاحقة تُتجاهل.</p>\n</aside>\n<p>صار لـ Watchlist مصدر واحد للحقيقة وثلاثة components تقرؤه وتغيّره. في الدرس التالي تزامن هذا الـ state مع شيء خارج React، أي عنوان تبويب المتصفّح وlocalStorage، باستخدام الـ effects.</p>\n","toc":[{"id":"sec-1","text":"المشكلة: الإخوة لا يتشاركون","level":2},{"id":"sec-2","text":"الحلّ: ارفعه إلى الأب المشترك","level":2},{"id":"sec-3","text":"البيانات تنزل، والأحداث تصعد","level":2},{"id":"sec-4","text":"علامات أنك تحتاج إلى الرفع","level":2},{"id":"sec-5","text":"في أدنى مكان ممكن، وأعلى مكان ضروري","level":2}],"takeaways":["حين يحتاج componentان إلى البيانات المتغيّرة نفسها، انقل الـ state إلى أقرب أب مشترك لهما ومرّره إلى الأسفل كـ props.","يغيّر الأبناء الـ state المشترك باستدعاء props من نوع المعالجات مثل <code dir=\"ltr\">onAdd</code> أو <code dir=\"ltr\">onFilterChange</code>؛ البيانات تنزل والأحداث تصعد.","أبقِ كل قطعة state في أدنى مكان ممكن من الشجرة، لكن في أعلى مكان ضروري.","لا تنسخ prop إلى state باستخدام <code dir=\"ltr\">useState(prop)</code>؛ فالنسخة تتجاهل التغييرات اللاحقة. استخدم الـ prop مباشرة أو اشتقّ منه."],"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> بـ <code dir=\"ltr\">const [filter, setFilter] = useState('all')</code>. ويحتاج أخوه <code dir=\"ltr\">MovieList</code> إلى عرض الأفلام المطابقة للفلتر فقط. ما الإصلاح؟</p>","options":[{"text":"اجعل FilterBar يكتب الفلتر في متغيّر عام يقرؤه MovieList.","why":"تغيير متغيّر في الـ module لا يُطلق أي re-render، فيعرض MovieList نتائج قديمة."},{"text":"امنح MovieList نسخته الخاصة من state الفلتر، وأبقِ النسختين متزامنتين بـ effect.","why":"نسختان من الـ state نفسه يجب مزامنتهما هما بالضبط المشكلة التي يحلّها رفع الـ state. كود أكثر وأخطاء أكثر."},{"text":"انقل state الفلتر إلى أبيهما المشترك، ومرّر <code dir=\"ltr\">filter</code> إلى كليهما، ومرّر <code dir=\"ltr\">setFilter</code> (أو معالجًا) إلى FilterBar.","why":"صحيح. مالك واحد ومصدر واحد للحقيقة. يعرض FilterBar القيمة ويغيّرها، ويقرؤها MovieList."},{"text":"اجعل MovieList ابنًا لـ FilterBar حتى يستطيع قراءة الـ state.","why":"إعادة ترتيب شجرة الواجهة لتناسب البيانات أمر معكوس، وستبقى الترويسة التي تعدّ الأفلام خارج الحلّ."}],"answer":2},{"q":"<p>أيّ قطعة من state في Watchlist يجب أن تبقى داخل <code dir=\"ltr\">AddMovieForm</code> بدل رفعها إلى <code dir=\"ltr\">App</code>؟</p>","options":[{"text":"النص المكتوب حاليًا في حقل العنوان.","why":"صحيح. لا يستخدم المسوّدة إلا النموذج. أمّا App فلا يحتاج إلا إلى الفيلم النهائي، الذي يصله عبر <code dir=\"ltr\">onAdd</code>."},{"text":"قائمة الأفلام.","why":"الترويسة والقائمة والنموذج كلها تعتمد على الأفلام، فمكانها App."},{"text":"الفلتر الحالي.","why":"أزرار الفلترة والقائمة كلتاهما تحتاجانه، فيعيش في أبيهما المشترك."}],"answer":0},{"q":"<p>يعرض <code dir=\"ltr\">MovieTitle</code> عنوانًا يستقبله كـ prop. وحين يغيّر الأب اسم الفيلم، يظل الابن يعرض العنوان القديم. لماذا؟</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=\"نسخ الكود\"><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>نسخ</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 على نصوص تتغيّر.","why":"يمكن أن تتغيّر الـ props في كل render. هذا الـ component هو من يتجاهل التغيير."},{"text":"نسي الأب استخدام key.","why":"تغيير الـ key كان سيعيد التركيب ويُخفي الخطأ، لكن الخطأ في الـ state المنسوخ، لا في الأب."},{"text":"يحتاج الـ h2 إلى معالج <code dir=\"ltr\">onChange</code>.","why":"العناوين ليست لها أحداث تغيير. المشكلة في مصدر النص."},{"text":"لا تستخدم <code dir=\"ltr\">useState</code> وسيطها إلا في الـ render الأول، فتكون <code dir=\"ltr\">text</code> نسخة مجمّدة. اعرض <code dir=\"ltr\">{title}</code> مباشرة.","why":"صحيح. القيمة الابتدائية تُقرأ مرة واحدة. وإذا لم يكن الـ component بحاجة إلى تعديل القيمة، فلا ينبغي أن ينسخها إلى الـ state."}],"answer":3},{"q":"<p>في Watchlist تنزل البيانات كـ props وتصعد التغييرات عبر الـ callbacks. ما الفائدة الأساسية لهذا التدفّق باتجاه واحد؟</p>","options":[{"text":"يجعل React ترسم أسرع من الربط ثنائي الاتجاه.","why":"الأداء ليس الهدف؛ الفائدة تتعلّق بفهم تطبيقك وتتبّع أخطائه."},{"text":"لتعرف لماذا يعرض شيء ما قيمة معيّنة، تتبع الـ props صعودًا إلى الـ component الوحيد الذي يملك الـ state.","why":"صحيح. لكل قطعة state مالك واحد، ووحده يغيّرها، فيكون للأخطاء مكان واحد تبحث فيه."},{"text":"يلغي الحاجة إلى معالجات الأحداث.","why":"الـ callbacks معالجات أحداث تُمرَّر إلى الأسفل، والتدفّق باتجاه واحد يعتمد عليها."}],"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":"ارفع الفلتر","prompt":"<p>يظهر زر الفلترة النشط مميّزًا بشكل صحيح، لكن القائمة لا تتغيّر أبدًا، لأن <code dir=\"ltr\">FilterBar</code> يحتفظ بالفلتر لنفسه.</p>\n<p>ارفع state الفلتر إلى <code dir=\"ltr\">App</code>. مرّر القيمة الحالية وطريقة لتغييرها إلى <code dir=\"ltr\">FilterBar</code> (يجب ألّا يكون له state خاص به)، وأعطِ <code dir=\"ltr\">MovieList</code> الأفلام المطابقة فقط:</p>\n<ul>\n<li><strong>All</strong>: كل الأفلام</li>\n<li><strong>To watch</strong>: قيمة <code dir=\"ltr\">watched</code> هي <code dir=\"ltr\">false</code></li>\n<li><strong>Watched</strong>: قيمة <code dir=\"ltr\">watched</code> هي <code dir=\"ltr\">true</code></li>\n</ul>\n<p>أبقِ <code dir=\"ltr\">aria-pressed</code> على الأزرار تعمل.</p>\n","hints":["انقل <code dir=\"ltr\">const [filter, setFilter] = useState('all')</code> من FilterBar إلى App.","امنح FilterBar اثنين من الـ props، مثل <code dir=\"ltr\">value</code> و<code dir=\"ltr\">onChange</code>، واستخدمهما مكان الـ state القديم.","في App، اشتقّ القائمة: <code dir=\"ltr\">MOVIES.filter((m) =&gt; filter === 'all' ? true : filter === 'watched' ? m.watched : !m.watched)</code>."],"explanation":"<p>الـ state خاص بالـ component الذي يعلن عنه، فلا يستطيع أخوه قراءته أبدًا. نقل <code dir=\"ltr\">filter</code> إلى <code dir=\"ltr\">App</code>، أقرب أب مشترك، يمنحه مالكًا واحدًا. ويصبح <code dir=\"ltr\">FilterBar</code> component متحكَّمًا به يعرض <code dir=\"ltr\">value</code> ويبلّغ عن النقرات عبر <code dir=\"ltr\">onChange</code>، ويشتقّ <code dir=\"ltr\">App</code> الأفلام الظاهرة من <code dir=\"ltr\">MOVIES</code> و<code dir=\"ltr\">filter</code> في كل render بدل تخزين نسخة مصفّاة.</p>\n","checkLabels":{"starts-all":"يبدأ على All ويعرض الأفلام الثلاثة","to-watch":"يعرض To watch الفيلمين Past Lives وDune: Part Two، ويكون مضغوطًا","watched":"يعرض Watched فيلم Arrival وحده، ويكون مضغوطًا","back-to-all":"يعرض All كل الأفلام مجددًا"}},"words":618,"readMinutes":4}