{"id":"l-rf-2-5","courseId":"react-fundamentals","lang":"en","kind":"lesson","summary":"Render lists from arrays with map, filter and sort them without mutating data, and choose keys that keep each item’s identity and state correct between renders.","html":"<p>The Watchlist is a list. So is a feed, a table of orders, a set of search results, a menu. Most UI is lists of things built from arrays of data, and React’s approach is the one you’d guess from JavaScript: transform the array of data into an array of elements.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">From data to elements with <code dir=\"ltr\">map</code></h2>\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\">  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>\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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.year</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:#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><code dir=\"ltr\">map</code> returns an array of <code dir=\"ltr\">&lt;li&gt;</code> elements, and React renders arrays by rendering each item in order. There’s no special loop syntax to learn; it’s the array method you already know.</p>\n<p>Filtering and sorting are the same idea: prepare the array, then map it. Do it during render, from the data you have. The visible list is derived, so it doesn’t need to be state.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"Run this example\"><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=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><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\">const</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\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'a1'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Arrival'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2016</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'p2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Past Lives'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2023</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, 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\">  { id: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'d3'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dune: Part Two'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2024</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, 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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> toWatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> movies</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#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 style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSorted</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.year </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.year);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(toWatch.</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\">m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> m.title)); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// [\"Dune: Part Two\", \"Past Lives\"]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movies[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].title);             </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Arrival (the original order is untouched)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p><code dir=\"ltr\">toSorted</code> returns a sorted copy and is supported in every current browser. The older <code dir=\"ltr\">sort</code> reorders the array <strong>in place</strong>. Called on state or props, that silently mutates data you don’t own. If you need to support older browsers, copy first: <code dir=\"ltr\">[...movies].sort(…)</code>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">What keys are for</h2>\n<p>Every element in a list needs a <code dir=\"ltr\">key</code> prop, and React warns in the console when one is missing. The reason becomes clear when the list changes.</p>\n<p>When React re-renders a list, it has the old array of elements and the new one. It needs to match them up: which new element is the same item as which old one? That matters because each item may have <strong>state</strong>, such as a half-typed note in an input, a focused button or an open menu, and that state must stay with the right item.</p>\n<p>The key is the item’s name tag. React matches old and new elements by key. Same key: it’s the same item, so React keeps its DOM and state and updates what changed. New key: a new item, so React creates it. Missing key: the item was removed, so React destroys it.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 290\" role=\"img\" aria-labelledby=\"l-rf-2-5-t1\"><defs><marker id=\"arrow-l-rf-2-5-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-2-5-t1\">Before: Arrival has a note. After adding Dune at the top with index keys, key 0 now points to Dune, so the note jumps to Dune. With id keys, the note stays with Arrival.</title>\n  <text class=\"d-label-strong\" x=\"110\" y=\"24\" text-anchor=\"middle\">Before</text>\n  <rect class=\"d-box\" x=\"20\" y=\"40\" width=\"180\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"110\" y=\"65\" text-anchor=\"middle\">0 · Arrival · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"20\" y=\"90\" width=\"180\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"110\" y=\"115\" text-anchor=\"middle\">1 · Past Lives</text>\n  <text class=\"d-label-strong\" x=\"350\" y=\"24\" text-anchor=\"middle\">key={index}</text>\n  <rect class=\"d-box-warn\" x=\"255\" y=\"40\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"65\" text-anchor=\"middle\">0 · Dune · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"255\" y=\"90\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"115\" text-anchor=\"middle\">1 · Arrival</text>\n  <rect class=\"d-box\" x=\"255\" y=\"140\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"350\" y=\"165\" text-anchor=\"middle\">2 · Past Lives</text>\n  <text class=\"d-label-strong\" x=\"590\" y=\"24\" text-anchor=\"middle\">key={movie.id}</text>\n  <rect class=\"d-box\" x=\"495\" y=\"40\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"65\" text-anchor=\"middle\">d3 · Dune</text>\n  <rect class=\"d-box-success\" x=\"495\" y=\"90\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"115\" text-anchor=\"middle\">a1 · Arrival · \"Loved it\"</text>\n  <rect class=\"d-box\" x=\"495\" y=\"140\" width=\"190\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"590\" y=\"165\" text-anchor=\"middle\">p2 · Past Lives</text>\n  <path class=\"d-arrow\" d=\"M200 60 L253 60\" marker-end=\"url(#arrow-l-rf-2-5-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M200 60 C 330 220, 420 120, 493 110\" marker-end=\"url(#arrow-l-rf-2-5-1)\"/>\n  <text class=\"d-label-muted\" x=\"350\" y=\"230\" text-anchor=\"middle\">Same key, same row: state follows the key</text>\n  <text class=\"d-label-muted\" x=\"350\" y=\"256\" text-anchor=\"middle\">Index keys follow position; id keys follow the movie</text>\n</svg><figcaption>Index keys versus id keys after inserting at the top</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Choosing a key</h2>\n<p>A good key is <strong>unique among siblings</strong> and <strong>stable</strong>: the same item has the same key on every render.</p>\n<ul>\n<li><strong>Use an id from your data.</strong> Database rows and API objects already have one.</li>\n<li><strong>Create ids when items are created.</strong> For movies added through a form, generate the id in the handler that adds them: <code dir=\"ltr\">{ id: crypto.randomUUID(), title, year }</code>. It’s stored on the object and never changes.</li>\n<li><strong>Never generate keys during render.</strong> <code dir=\"ltr\">key={Math.random()}</code> or <code dir=\"ltr\">key={crypto.randomUUID()}</code> inside <code dir=\"ltr\">map</code> produces new keys every render. React treats every row as brand new, tears down the DOM and loses input focus and state each time you type.</li>\n<li><strong>Don’t use content that isn’t unique.</strong> Titles repeat; there are several films called “Dune”.</li>\n</ul>\n<p>Keys only need to be unique among siblings in the same list. Two different lists can use the same ids.</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>Index as key on a list that changes</span></p>\n<p><code dir=\"ltr\">movies.map((movie, index) =&gt; &lt;li key={index}&gt;…)</code> silences the warning and works until the list changes order. Insert at the top, delete from the middle, sort or filter, and React matches rows by position. State, focus and uncontrolled input values stay at the position while the data moves. Index keys are only safe for static lists that never reorder, like a fixed set of footer links.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Keys on components and fragments</h2>\n<p>The key goes on the outermost element returned from the <code dir=\"ltr\">map</code> callback. When that’s a component, put it on the component:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JSX</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{movies.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">movie</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieItem</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:#0550AE;--shiki-dark:#79C0FF\"> movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">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:#1F2328;--shiki-dark:#E6EDF3\">))}</span></span></code></pre></div>\n<p><code dir=\"ltr\">MovieItem</code> can’t read <code dir=\"ltr\">key</code>; React uses it and doesn’t pass it down. If the component needs the id, pass it separately as a prop.</p>\n<p>When each item renders several siblings without a wrapper, use the long form of a fragment, which accepts a key:</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:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Fragment } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react'</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\">{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\">Fragment</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">dt</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\">dt</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\">dd</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.year</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\">dd</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\">Fragment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))}</span></span></code></pre></div>\n<p>The short <code dir=\"ltr\">&lt;&gt;…&lt;/&gt;</code> syntax can’t take a key.</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>Keys can reset a component on purpose</span></p>\n<p>Because a new key means a new component, changing a key forces React to start over. <code dir=\"ltr\">&lt;MovieDetails key={selectedId} … /&gt;</code> gives each selected movie a fresh details panel with fresh state. It’s a handy trick you’ll see again.</p>\n</aside>\n<p>You can now render any list correctly. That completes the static Watchlist. In the next section, it starts to move: state lets the list change when the user clicks, types and submits.</p>\n","toc":[{"id":"sec-1","text":"From data to elements with map","level":2},{"id":"sec-2","text":"What keys are for","level":2},{"id":"sec-3","text":"Choosing a key","level":2},{"id":"sec-4","text":"Keys on components and fragments","level":2}],"takeaways":["Turn an array of data into an array of elements with <code dir=\"ltr\">map</code>, and put the <code dir=\"ltr\">key</code> on the outermost element the callback returns.","A key tells React which item is which between renders, so it must be unique among siblings and stable over time.","Use an id from your data as the key; create ids when items are created, never during render.","Index keys break when the list can be reordered, filtered or inserted into, because state sticks to the position instead of the item.","Filter and sort copies of your data while rendering; <code dir=\"ltr\">sort</code> changes the array in place, so use <code dir=\"ltr\">toSorted</code> or sort a copy."],"further":[{"title":"Rendering Lists","url":"https://react.dev/learn/rendering-lists"},{"title":"Preserving and Resetting State","url":"https://react.dev/learn/preserving-and-resetting-state"},{"title":"Array.prototype.toSorted() on MDN","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted"}],"quiz":[{"q":"<p>Where does the key go?</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\"> MovieList</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </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\">  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\">MovieItem</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movie</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">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:#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","options":[{"text":"On the <code dir=\"ltr\">&lt;li&gt;</code> inside MovieItem, as <code dir=\"ltr\">&lt;li key={movie.id}&gt;</code>.","why":"React needs keys on the elements in the array you return from <code dir=\"ltr\">map</code>. Inside MovieItem there’s only one <code dir=\"ltr\">&lt;li&gt;</code>, so a key there does nothing."},{"text":"On the <code dir=\"ltr\">&lt;ul&gt;</code>, as <code dir=\"ltr\">&lt;ul key=&quot;movies&quot;&gt;</code>.","why":"The <code dir=\"ltr\">&lt;ul&gt;</code> isn’t one of many siblings. The items inside it are what React has to tell apart."},{"text":"On MovieItem itself, as <code dir=\"ltr\">&lt;MovieItem key={movie.id} movie={movie} /&gt;</code>.","why":"Correct. The key belongs on the outermost element returned from the <code dir=\"ltr\">map</code> callback, which here is the component."},{"text":"Nowhere, because components get automatic keys.","why":"React falls back to the index and warns in development. That fallback is exactly what causes bugs when the list changes."}],"answer":2},{"q":"<p>Each movie row has a text input for a note. You type “Loved it” next to Arrival, then add a new movie to the top of the list. With <code dir=\"ltr\">key={index}</code>, where does “Loved it” end up?</p>","options":[{"text":"Still next to Arrival.","why":"That’s what you’d get with <code dir=\"ltr\">key={movie.id}</code>. With index keys React matches rows by position, not by movie."},{"text":"Next to the new movie at the top, because it now has index 0, the key Arrival’s row had.","why":"Correct. React reuses the row with key 0, including its input’s DOM state, for whatever is now at position 0."},{"text":"It disappears, because every row is re-created.","why":"React doesn’t re-create rows whose keys still exist. It reuses them, which is precisely why the note moves."},{"text":"React throws a duplicate key error.","why":"Indices are unique, so there’s no duplicate. The bug is silent, which is what makes it dangerous."}],"answer":1},{"q":"<p>Which key is a good choice for movies the user adds through a form?</p>","options":[{"text":"<code dir=\"ltr\">key={Math.random()}</code> in the map callback.","why":"A new key on every render makes React destroy and re-create every row each time, losing focus and state."},{"text":"<code dir=\"ltr\">key={movie.title}</code>","why":"Titles aren’t unique; there are several films called “Dune”. Two siblings with the same key confuse React."},{"text":"<code dir=\"ltr\">key={index}</code>, because the user only adds movies.","why":"Users will also remove, filter and sort. Index keys break as soon as items move relative to each other."},{"text":"An id created once with <code dir=\"ltr\">crypto.randomUUID()</code> when the movie is added, and stored on the movie object.","why":"Correct. The id is created with the data, stays the same for the movie’s lifetime, and is unique."}],"answer":3},{"q":"<p><code dir=\"ltr\">movies</code> comes from state. What’s the problem with <code dir=\"ltr\">const sorted = movies.sort((a, b) =&gt; b.year - a.year);</code> in the render code?</p>","options":[{"text":"<code dir=\"ltr\">sort</code> reorders <code dir=\"ltr\">movies</code> in place, so you’re mutating state during render.","why":"Correct. Use <code dir=\"ltr\">movies.toSorted(…)</code> or <code dir=\"ltr\">[...movies].sort(…)</code> to sort a copy and leave state untouched."},{"text":"<code dir=\"ltr\">sort</code> can’t compare numbers.","why":"With a compare function like <code dir=\"ltr\">(a, b) =&gt; b.year - a.year</code>, sort orders numbers correctly."},{"text":"Sorting must happen in an effect, not during render.","why":"Calculating a derived value during render is exactly right. The issue is changing the original array."}],"answer":0}],"exercise":{"kind":"web","mode":"react","starter":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival', year: 2016 },\n  { id: 'p2', title: 'Past Lives', year: 2023 },\n];\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n\n  function addDune() {\n    setMovies([{ id: 'd3', title: 'Dune: Part Two', year: 2024 }, ...movies]);\n  }\n\n  // 1. Show the movies newest first, without changing `movies` itself.\n  // 2. Fix the key so each note stays with its movie.\n  return (\n    <main>\n      <button onClick={addDune}>Add Dune: Part Two</button>\n      <ul>\n        {movies.map((movie, index) => (\n          <li key={index}>\n            <span>{movie.title} ({movie.year})</span>\n            <input aria-label=\"Note\" placeholder=\"Note\" />\n          </li>\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"solution":{"jsx":"import { useState } from 'react';\n\nconst INITIAL = [\n  { id: 'a1', title: 'Arrival', year: 2016 },\n  { id: 'p2', title: 'Past Lives', year: 2023 },\n];\n\nexport default function App() {\n  const [movies, setMovies] = useState(INITIAL);\n\n  function addDune() {\n    setMovies([{ id: 'd3', title: 'Dune: Part Two', year: 2024 }, ...movies]);\n  }\n\n  const newestFirst = movies.toSorted((a, b) => b.year - a.year);\n\n  return (\n    <main>\n      <button onClick={addDune}>Add Dune: Part Two</button>\n      <ul>\n        {newestFirst.map((movie) => (\n          <li key={movie.id}>\n            <span>{movie.title} ({movie.year})</span>\n            <input aria-label=\"Note\" placeholder=\"Note\" />\n          </li>\n        ))}\n      </ul>\n    </main>\n  );\n}\n"},"checks":[{"id":"sorted","test":"const titles = $$('li').map((li) => text(li.querySelector('span')).replace(/\\s*\\(\\d+\\)$/, '')); return titles.join('|') === 'Past Lives|Arrival';"},{"id":"note-follows-movie","test":"const row = (t) => $$('li').find((li) => text(li.querySelector('span')).startsWith(t)); await input(row('Arrival').querySelector('input'), 'Loved it'); await click($('button')); const a = row('Arrival'); const d = row('Dune'); return !!a && !!d && a.querySelector('input').value === 'Loved it' && d.querySelector('input').value === '';"},{"id":"sorted-after-add","test":"const titles = $$('li').map((li) => text(li.querySelector('span')).replace(/\\s*\\(\\d+\\)$/, '')); return titles.join('|') === 'Dune: Part Two|Past Lives|Arrival';"}],"title":"Keys that keep notes in place","prompt":"<p>Each movie row has a note input. Two problems:</p>\n<ol>\n<li>The list should show movies <strong>newest first</strong> (by <code dir=\"ltr\">year</code>). Sort a copy during render; don’t change the <code dir=\"ltr\">movies</code> state.</li>\n<li>Type a note next to Arrival, then click <strong>Add Dune: Part Two</strong>. The note jumps to the wrong movie. Fix the key so each note stays with its movie.</li>\n</ol>\n<p>Keep the markup the same: one <code dir=\"ltr\">&lt;li&gt;</code> per movie with a <code dir=\"ltr\">&lt;span&gt;</code> (title and year) and an <code dir=\"ltr\">&lt;input&gt;</code>.</p>\n","hints":["<code dir=\"ltr\">movies.toSorted((a, b) =&gt; b.year - a.year)</code> returns a new, sorted array and leaves <code dir=\"ltr\">movies</code> alone.","Map over the sorted copy, not over <code dir=\"ltr\">movies</code>.","Each movie already has a stable <code dir=\"ltr\">id</code>. Use <code dir=\"ltr\">key={movie.id}</code> instead of the index."],"explanation":"<p>With <code dir=\"ltr\">key={index}</code>, React matches rows by position. Adding a movie shifts everything down, so the row that used to be Arrival’s (key 0 in the starter) is reused for whichever movie now sits at that position, with the note still in its input. With <code dir=\"ltr\">key={movie.id}</code>, React knows the Arrival row is the same item as before and moves it, input value and all. Sorting a copy during render keeps <code dir=\"ltr\">movies</code> as the single source of truth; the newest-first order is derived from it every time.</p>\n","checkLabels":{"sorted":"Movies appear newest first: Past Lives, then Arrival","note-follows-movie":"After adding Dune, Arrival’s note is still next to Arrival","sorted-after-add":"Dune: Part Two (2024) appears first after it is added"}},"words":695,"readMinutes":3}