{"id":"l-rf-2-4","courseId":"react-fundamentals","lang":"en","kind":"lesson","summary":"Show, hide and switch UI based on data using early returns, the ternary operator and &amp;&amp;, while avoiding the stray “0” bug and unreadable nested conditions.","html":"<p>Real interfaces change shape with the data. The Watchlist shows a “Watched” badge on some movies but not others, an encouraging message when the list is empty, and a disabled Add button until you type a title. None of that needs special React syntax. It’s plain JavaScript deciding which JSX to return, and there are three tools for it.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Early return: whole alternatives</h2>\n<p>When a component shows something entirely different in one case, handle that case first and return:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/components/MovieList.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\"> MovieList</span><span style=\"color:#953800;--shiki-dark:#FFA657\">({ movies }) </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\"> (movies.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</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\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"empty\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Nothing to watch yet. Add a movie above.&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</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\">ul</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\">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 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\">li</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></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\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ul</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 empty case is dealt with in two lines, and the rest of the function only worries about the normal case. This is the most readable pattern, so reach for it first. (The <code dir=\"ltr\">map</code> and <code dir=\"ltr\">key</code> are the topic of the next lesson.)</p>\n<p>A component can also return <code dir=\"ltr\">null</code> to render nothing at all:</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:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> WatchedBadge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">watched</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\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">watched) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</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:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"badge\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Watched&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</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<h2 id=\"sec-2\" tabindex=\"-1\">Ternary: one thing or another</h2>\n<p>Inside JSX, where you can’t write <code dir=\"ltr\">if</code>, use the conditional operator for a choice between two outputs:</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\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</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.watched </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:#0A3069;--shiki-dark:#A5D6FF\"> 'To watch'</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\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>It works for elements too: <code dir=\"ltr\">{isEditing ? &lt;TitleInput /&gt; : &lt;h2&gt;{title}&lt;/h2&gt;}</code>. Keep ternaries short. Once you nest one ternary inside another, or the branches run to several lines each, move the decision into a variable above the <code dir=\"ltr\">return</code>, or into its own small component.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\"><code dir=\"ltr\">&amp;&amp;</code>: something or nothing</h2>\n<p>When the alternative is “nothing”, the logical AND operator is shorter than a ternary with <code dir=\"ltr\">null</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"3\"><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\">li</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:#CF222E;--shiki-dark:#FF7B72\">  {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.watched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"badge\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Watched&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</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\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>JavaScript evaluates <code dir=\"ltr\">a &amp;&amp; b</code> to <code dir=\"ltr\">a</code> if <code dir=\"ltr\">a</code> is falsy, otherwise to <code dir=\"ltr\">b</code>. When <code dir=\"ltr\">movie.watched</code> is <code dir=\"ltr\">false</code>, the expression is <code dir=\"ltr\">false</code>, and React renders nothing for <code dir=\"ltr\">false</code>. When it’s <code dir=\"ltr\">true</code>, you get the badge.</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>The stray zero</span></p>\n<p><code dir=\"ltr\">{movies.length &amp;&amp; &lt;MovieList movies={movies} /&gt;}</code> looks right, but when the list is empty, <code dir=\"ltr\">0 &amp;&amp; …</code> evaluates to <code dir=\"ltr\">0</code>, and React renders numbers. Your page shows a lonely “0”. Make the left side a real boolean: <code dir=\"ltr\">{movies.length &gt; 0 &amp;&amp; …}</code>. The same trap waits for any number on the left of <code dir=\"ltr\">&amp;&amp;</code>, such as a count or an index.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Conditions in attributes</h2>\n<p>Conditions aren’t only about whole elements. Attributes take expressions too:</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:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.watched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'movie movie-watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'movie'</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\">li</span><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\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> disabled</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">title.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">trim</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ''</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Add&#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:#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:#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></code></pre></div>\n<p>The third one matters for accessibility: <code dir=\"ltr\">aria-pressed</code> tells screen reader users which toggle button is active, which a color change alone doesn’t. When a value is <code dir=\"ltr\">false</code> or <code dir=\"ltr\">null</code> for a boolean attribute like <code dir=\"ltr\">disabled</code>, React leaves it off the element.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Picking the right tool</h2>\n<p>All three patterns produce the same kind of result, so the choice is about readability. A rule of thumb that holds up well in code review:</p>\n<ul>\n<li><strong>The whole output changes</strong> (loading, error, empty, normal): early return, one case at a time, top to bottom.</li>\n<li><strong>One spot switches between two things</strong>: a ternary, kept to a line or two per branch.</li>\n<li><strong>One spot is either there or not</strong>: <code dir=\"ltr\">&amp;&amp;</code> with a real boolean on the left.</li>\n</ul>\n<p>When you notice yourself writing a ternary inside a ternary, or an <code dir=\"ltr\">&amp;&amp;</code> chain with three conditions, stop and give the logic a name. Either compute a variable (<code dir=\"ltr\">const status = …</code>) above the <code dir=\"ltr\">return</code>, or extract a small component such as <code dir=\"ltr\">WatchedBadge</code> that hides the decision inside itself. The parent then reads like a sentence: title, badge, actions.</p>\n<p>Order matters with early returns, too. Put the cases that make the rest impossible first. If a movie list can be loading, failed or empty, check them in that order, so the final <code dir=\"ltr\">return</code> only ever deals with real data.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">Not rendering versus hiding</h2>\n<p>There’s an important difference between these two:</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\">{showWatched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">WatchedPanel</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\"> hidden</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">showWatched</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\">WatchedPanel</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></code></pre></div>\n<p>The first removes <code dir=\"ltr\">WatchedPanel</code> from the tree when the condition is false. React <strong>unmounts</strong> it: its DOM goes away and any state inside it, such as text typed into an input, is destroyed. When it comes back, it starts fresh. The second keeps it mounted and only hides its wrapper (the HTML <code dir=\"ltr\">hidden</code> attribute works like <code dir=\"ltr\">display: none</code>), so the state survives.</p>\n<p>Most of the time you want the first; there’s no reason to keep invisible UI alive. Choose hiding when the user expects their in-progress work inside the hidden part to still be there, as in tabs with half-filled forms.</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>Name the condition</span></p>\n<p><code dir=\"ltr\">{allWatched &amp;&amp; …}</code> reads better than <code dir=\"ltr\">{movies.filter((m) =&gt; !m.watched).length === 0 &amp;&amp; …}</code>. Compute a well-named boolean above the <code dir=\"ltr\">return</code> and the JSX stays readable.</p>\n</aside>\n<p>Next, you’ll render the whole movie list from data with <code dir=\"ltr\">map</code>, and learn why React keeps asking you for a <code dir=\"ltr\">key</code>.</p>\n","toc":[{"id":"sec-1","text":"Early return: whole alternatives","level":2},{"id":"sec-2","text":"Ternary: one thing or another","level":2},{"id":"sec-3","text":"&&: something or nothing","level":2},{"id":"sec-4","text":"Conditions in attributes","level":2},{"id":"sec-5","text":"Picking the right tool","level":2},{"id":"sec-6","text":"Not rendering versus hiding","level":2}],"takeaways":["Use an early <code dir=\"ltr\">return</code> for whole alternative screens, a ternary for one of two things, and <code dir=\"ltr\">&amp;&amp;</code> for something or nothing.","A component can return <code dir=\"ltr\">null</code> to render nothing at all.","<code dir=\"ltr\">{count &amp;&amp; &lt;List /&gt;}</code> renders a literal 0 when count is 0; compare explicitly with <code dir=\"ltr\">count &gt; 0 &amp;&amp;</code>.","Conditions can drive attributes too, such as <code dir=\"ltr\">className</code>, <code dir=\"ltr\">disabled</code> and <code dir=\"ltr\">aria-pressed</code>.","Not rendering a component removes it and its state; hiding it with CSS keeps both."],"further":[{"title":"Conditional Rendering","url":"https://react.dev/learn/conditional-rendering"},{"title":"Logical AND (&&) on MDN","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND"}],"quiz":[{"q":"<p><code dir=\"ltr\">movies</code> is an empty array. What does <code dir=\"ltr\">&lt;div&gt;{movies.length &amp;&amp; &lt;MovieList movies={movies} /&gt;}&lt;/div&gt;</code> show?</p>","options":[{"text":"Nothing at all.","why":"That’s what you’d want, but <code dir=\"ltr\">&amp;&amp;</code> returns the left side when it’s falsy, and the left side here is the number 0."},{"text":"An empty list.","why":"MovieList never renders, because <code dir=\"ltr\">0 &amp;&amp; …</code> stops at 0."},{"text":"An error about rendering a number.","why":"Numbers are valid React children, which is exactly why the 0 shows up instead of an error."},{"text":"The character “0”.","why":"Correct. <code dir=\"ltr\">0 &amp;&amp; x</code> evaluates to 0, and React renders numbers as text. Write <code dir=\"ltr\">movies.length &gt; 0 &amp;&amp; …</code> instead."}],"answer":3},{"q":"<p><code dir=\"ltr\">MovieList</code> should show a full-page “Nothing to watch yet” message when the list is empty, and the list otherwise. Which structure reads best?</p>","options":[{"text":"An early return: <code dir=\"ltr\">if (movies.length === 0) return &lt;EmptyState /&gt;;</code> followed by the normal list markup.","why":"Correct. The empty case is handled and done; the rest of the function only deals with the normal case, without nesting."},{"text":"A ternary wrapping the entire component’s JSX, with another ternary nested inside for each item’s badge.","why":"It works, but nested ternaries in big JSX blocks are hard to read and easy to break when editing."},{"text":"Render both and hide the wrong one with <code dir=\"ltr\">style={{ display: 'none' }}</code>.","why":"Both trees stay mounted and run, and you now have two places to keep in sync. Render only what’s needed."}],"answer":0},{"q":"<p><code dir=\"ltr\">WatchedBadge</code> should render nothing when <code dir=\"ltr\">watched</code> is false. Which version does that?</p>","options":[{"text":"<code dir=\"ltr\">if (!watched) return;</code> and nothing else for that case","why":"A bare <code dir=\"ltr\">return</code> gives <code dir=\"ltr\">undefined</code>. React 19 accepts it, but <code dir=\"ltr\">return null</code> states the intent clearly and is the convention you’ll see everywhere."},{"text":"<code dir=\"ltr\">if (!watched) return &lt;&gt;&lt;/&gt;;</code>","why":"An empty fragment also renders nothing, but it’s noisier than the conventional <code dir=\"ltr\">return null</code>."},{"text":"<code dir=\"ltr\">if (!watched) return null;</code>","why":"Correct. Returning <code dir=\"ltr\">null</code> is the standard, explicit way for a component to render nothing."},{"text":"<code dir=\"ltr\">if (!watched) return false;</code>","why":"A component returning <code dir=\"ltr\">false</code> happens to render nothing, but it reads like a bug. Return <code dir=\"ltr\">null</code>."}],"answer":2},{"q":"<p>A collapsible “Watched” panel contains a text input. A user types a note, collapses the panel with <code dir=\"ltr\">{open &amp;&amp; &lt;Panel /&gt;}</code>, and expands it again. What happened to the note?</p>","options":[{"text":"It’s still there, because React caches unmounted components.","why":"React doesn’t keep unmounted components around. When the condition is false, the Panel and its state are destroyed."},{"text":"It’s gone, because the panel was removed from the tree and mounted fresh.","why":"Correct. Conditional rendering unmounts. If the state must survive, keep it in a parent or hide the panel with CSS instead."},{"text":"It’s still there, because the browser restores input values.","why":"Browser form restoration applies to page navigation, not to DOM nodes that React removed and recreated."}],"answer":1}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"function MovieList({ movies }) {\n  return (\n    <div>\n      {movies.length && (\n        <ul>\n          {movies.map((movie) => (\n            <li key={movie.id}>\n              {movie.title}\n              <span className=\"badge\">Watched</span>\n            </li>\n          ))}\n        </ul>\n      )}\n    </div>\n  );\n}\n\nconst MOVIES = [\n  { id: 1, title: 'Arrival', watched: true },\n  { id: 2, title: 'Past Lives', watched: false },\n  { id: 3, title: 'Paddington 2', watched: false },\n];\n\nexport default function App() {\n  return (\n    <main>\n      <section id=\"full\">\n        <MovieList movies={MOVIES} />\n      </section>\n      <section id=\"empty\">\n        <MovieList movies={[]} />\n      </section>\n    </main>\n  );\n}\n"},"solution":{"jsx":"function MovieList({ movies }) {\n  if (movies.length === 0) {\n    return <p>Nothing to watch yet.</p>;\n  }\n\n  return (\n    <ul>\n      {movies.map((movie) => (\n        <li key={movie.id}>\n          {movie.title}\n          {movie.watched && <span className=\"badge\">Watched</span>}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nconst MOVIES = [\n  { id: 1, title: 'Arrival', watched: true },\n  { id: 2, title: 'Past Lives', watched: false },\n  { id: 3, title: 'Paddington 2', watched: false },\n];\n\nexport default function App() {\n  return (\n    <main>\n      <section id=\"full\">\n        <MovieList movies={MOVIES} />\n      </section>\n      <section id=\"empty\">\n        <MovieList movies={[]} />\n      </section>\n    </main>\n  );\n}\n"},"checks":[{"id":"full-list","test":"return $$('#full li').length === 3;"},{"id":"badge-watched","test":"const li = $$('#full li'); const b = li[0] && li[0].querySelector('span'); return !!b && text(b) === 'Watched';"},{"id":"badge-hidden","test":"const li = $$('#full li'); return li.length === 3 && !li[1].querySelector('span') && !li[2].querySelector('span') && !text(li[1]).includes('Watched') && !text(li[2]).includes('Watched');"},{"id":"empty-message","test":"return text('#empty').includes('Nothing to watch yet.') && !$('#empty ul');"},{"id":"no-zero","test":"return !/\\b0\\b/.test(text('#empty'));"}],"title":"Badges and an empty state","prompt":"<p><code dir=\"ltr\">MovieList</code> has three bugs. Fix them so that:</p>\n<ol>\n<li>The <strong>Watched</strong> badge (<code dir=\"ltr\">&lt;span className=&quot;badge&quot;&gt;Watched&lt;/span&gt;</code>) appears only on movies whose <code dir=\"ltr\">watched</code> is <code dir=\"ltr\">true</code>.</li>\n<li>When <code dir=\"ltr\">movies</code> is empty, the list renders the message <strong>Nothing to watch yet.</strong> instead of a list.</li>\n<li>The empty case never shows a stray <strong>0</strong>.</li>\n</ol>\n<p><code dir=\"ltr\">App</code> renders the list twice, once with three movies and once with none. Don’t change <code dir=\"ltr\">App</code>.</p>\n","hints":["Look at what <code dir=\"ltr\">movies.length &amp;&amp; …</code> evaluates to when the length is 0.","Handle the empty case first with an early return: <code dir=\"ltr\">if (movies.length === 0) return &lt;p&gt;…&lt;/p&gt;;</code>","For the badge, use <code dir=\"ltr\">{movie.watched &amp;&amp; &lt;span className=&quot;badge&quot;&gt;Watched&lt;/span&gt;}</code>."],"explanation":"<p>The early return handles the empty list completely, which removes the <code dir=\"ltr\">movies.length &amp;&amp; …</code> expression and its stray 0 in one move. For the badge, <code dir=\"ltr\">movie.watched &amp;&amp; &lt;span&gt;…&lt;/span&gt;</code> is safe because <code dir=\"ltr\">watched</code> is a boolean: when it’s <code dir=\"ltr\">false</code>, React renders nothing. Keep <code dir=\"ltr\">&amp;&amp;</code> for booleans; when the left side is a number, compare it explicitly (<code dir=\"ltr\">length &gt; 0</code>).</p>\n","checkLabels":{"full-list":"The full list still shows three movies","badge-watched":"Arrival (watched) shows the Watched badge","badge-hidden":"Past Lives and Paddington 2 don’t show a badge","empty-message":"The empty list shows “Nothing to watch yet.” and no <ul>","no-zero":"No stray 0 appears for the empty list"}},"words":704,"readMinutes":3}