π What is List Rendering?
List rendering means displaying a list of items (like an array of data) dynamically in the UI using .map().
React efficiently re-renders only the items that change, thanks to using keys.
π§ Key Points:
Use
.map()to iterate over an array and return JSX elements.Always add a unique
keyto each list item.keyhelps React identify which items changed, added, or removed.
<div class="infographic-listkeys" id="lk">
<style>
.infographic-listkeys {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--bad: #dc2626;
--bad-soft: #fde3e3;
--good: #16a34a;
--good-soft: #dcf5e3;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-listkeys[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
--bad-soft: #3a1414;
--good-soft: #103a1e;
}
.infographic-listkeys * { box-sizing: border-box; min-width: 0; }
.lk-eyebrow {
font-family: var(--mono-font); font-size: 0.72rem; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.45rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.lk-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.lk-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4vw, 2.15rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.18; letter-spacing: -0.01em;
}
.lk-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.8rem; }
.lk-grid { display: flex; flex-direction: column; gap: 1.5rem; align-items: stretch; margin-bottom: 1.3rem; }
.lk-code-head { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.lk-code-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.lk-code-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.lk-status {
font-family: var(--mono-font); font-size: 0.65rem; font-weight: 700; border-radius: 100px;
padding: 0.15rem 0.55rem; background: var(--bad-soft); color: var(--bad); transition: background 0.25s, color 0.25s;
}
.lk-status.is-fixed { background: var(--good-soft); color: var(--good); }
.lk-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem; overflow: auto; max-height: 340px;
}
.lk-code-block code { font-family: var(--mono-font); font-size: 0.72rem; line-height: 1.75; color: #d4d4d8; white-space: pre; }
.tok-kw { color: #c678dd; }
.tok-tag { color: #e06c75; }
.tok-type { color: #61afef; }
.tok-attr { color: #d19a66; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.tok-punc { color: #6b7280; }
.tok-comment { color: #7f848e; font-style: italic; }
.lk-highlight { background: rgba(34,197,94,0.3); border-radius: 3px; padding: 0 3px; margin: 0 -3px; transition: background 1.6s ease; }
.lk-highlight.is-settled { background: transparent; }
.lk-actions { display: flex; justify-content: center; gap: 0.7rem; margin-bottom: 1rem; flex-wrap: wrap; }
.lk-btn {
display: flex; align-items: center; gap: 0.5rem; font: inherit; font-weight: 600; font-size: 0.85rem;
color: #fff; background: var(--accent); border: none; border-radius: 100px; padding: 0.6rem 1.3rem;
cursor: pointer; box-shadow: 0 6px 16px -4px rgba(37,99,235,0.45); transition: transform 0.15s, background 0.15s, opacity 0.15s;
}
.lk-btn:hover:not(:disabled) { transform: translateY(-1px); }
.lk-btn:disabled { background: var(--good); opacity: 0.85; cursor: default; box-shadow: none; }
.lk-btn:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.lk-btn.lk-secondary { background: var(--ink); box-shadow: 0 6px 16px -4px rgba(26,26,26,0.35); }
.infographic-listkeys[data-theme="dark"] .lk-btn.lk-secondary { background: #384254; }
.lk-output-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; width: 100%; }
.lk-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
.lk-preview { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; width: 100%; box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18); }
.lk-preview-bar { background: #e9e9e9; padding: 0.45rem 0.6rem; display: flex; gap: 0.3rem; }
.lk-preview-bar span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.lk-preview-bar span:nth-child(1) { background: #ff5f56; }
.lk-preview-bar span:nth-child(2) { background: #ffbd2e; }
.lk-preview-bar span:nth-child(3) { background: #27c93f; }
.lk-preview-page { background: #fff; padding: 1.5rem 1.6rem; font-family: Times, "Times New Roman", serif; }
.lk-preview-page h1 { font-size: 1.15rem; margin: 0 0 0.9rem; color: #000; text-align: center; }
.lk-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.55rem; }
.lk-item {
display: flex; align-items: center; gap: 0.7rem; padding: 0.5rem 0.6rem; border-radius: 6px;
background: #f7f7f5; border: 1px solid #e5e2da; transition: background 0.4s ease, transform 0.4s ease;
}
.lk-item.lk-flash { background: #fde3e3; }
.lk-item-name { font-size: 1rem; color: #000; min-width: 68px; font-weight: 600; }
.lk-item input {
flex: 1; font-family: var(--body-font); font-size: 0.85rem; padding: 0.35rem 0.55rem;
border: 1px solid #ccc; border-radius: 5px; color: #000; background: #fff;
}
.lk-item input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.lk-caption {
text-align: center; font-size: 0.9rem; color: var(--ink); min-height: 2.4em; display: flex;
align-items: center; justify-content: center; max-width: 560px; margin: 0.9rem auto 1.6rem; line-height: 1.45;
}
.lk-takeaway {
padding: 0.95rem 1.2rem; background: var(--accent-soft); border-left: 3px solid var(--accent);
border-radius: 4px; font-size: 0.9rem; color: var(--ink); line-height: 1.5; max-width: 640px; margin: 0 auto;
}
.lk-takeaway b { color: var(--accent-deep); }
.lk-takeaway code { font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-listkeys *, .infographic-listkeys *::before, .infographic-listkeys *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="lk-eyebrow">Rendering Lists</div>
<h1 class="lk-title">Why List Keys Must Be Stable</h1>
<p class="lk-sub">Type a note next to each name, then shuffle β watch where the notes end up.</p>
<div class="lk-grid">
<div class="lk-code-block">
<div class="lk-code-head">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3 L2 8 L6 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3 L14 8 L10 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="lk-code-name">NameList.jsx</span>
<span class="lk-status" id="lkStatus">key={index}</span>
</div>
<pre><code id="lkCode"></code></pre>
</div>
<div class="lk-output-wrap">
<div class="lk-output-label">Output</div>
<div class="lk-preview">
<div class="lk-preview-bar"><span></span><span></span><span></span></div>
<div class="lk-preview-page">
<h1>List of Names</h1>
<ul class="lk-list" id="lkList"></ul>
</div>
</div>
</div>
</div>
<div class="lk-actions">
<button class="lk-btn lk-secondary" id="lkShuffleBtn" type="button">π Shuffle names</button>
<button class="lk-btn" id="lkFixBtn" type="button">π§ Use a stable key</button>
</div>
<p class="lk-caption" id="lkCaption">Add a note to each name, then shuffle to see what index keys do.</p>
<div class="lk-takeaway"><b>Index keys track position, not identity.</b> When the list reorders, React matches DOM nodes (and their input state) by <code>key</code> β with <code>key={index}</code> that's the slot, not the name, so state slides onto whoever moved there. A stable <code>key={name}</code> (or an id) keeps each row's state attached to the right item.</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('lk');
if (!root) return;
/* --- dark mode detection --- */
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') {
if (e.data.value) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
});
var brokenCode =
'<span class="tok-kw">import</span> <span class="tok-type">React</span> <span class="tok-kw">from</span> <span class="tok-str">"react"</span><span class="tok-punc">;</span>\n\n' +
'<span class="tok-kw">function</span> <span class="tok-type">NameList</span><span class="tok-punc">() {</span>\n' +
' <span class="tok-kw">const</span> names <span class="tok-punc">=</span> <span class="tok-punc">[</span><span class="tok-str">"Raj"</span><span class="tok-punc">,</span> <span class="tok-str">"Ravi"</span><span class="tok-punc">,</span> <span class="tok-str">"Sneha"</span><span class="tok-punc">];</span>\n\n' +
' <span class="tok-kw">return</span> <span class="tok-punc">(</span>\n' +
' <span class="tok-punc"><></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">h1</span><span class="tok-punc">></span>List of Names<span class="tok-punc"></</span><span class="tok-tag">h1</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">ul</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">{</span>names.map<span class="tok-punc">((</span>name<span class="tok-punc">,</span> index<span class="tok-punc">) => (</span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">li</span> <span class="lk-highlight" id="lkHighlight"><span class="tok-attr">key</span><span class="tok-punc">={</span>index<span class="tok-punc">}</span></span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">{</span>name<span class="tok-punc">}</span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">placeholder</span><span class="tok-punc">=</span><span class="tok-str">"note"</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"></</span><span class="tok-tag">li</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">))}</span>\n' +
' <span class="tok-punc"></</span><span class="tok-tag">ul</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"></></span>\n' +
' <span class="tok-punc">);</span>\n' +
'<span class="tok-punc">}</span>\n\n' +
'<span class="tok-kw">export</span> <span class="tok-kw">default</span> <span class="tok-type">NameList</span><span class="tok-punc">;</span>';
var fixedCode =
'<span class="tok-kw">import</span> <span class="tok-type">React</span> <span class="tok-kw">from</span> <span class="tok-str">"react"</span><span class="tok-punc">;</span>\n\n' +
'<span class="tok-kw">function</span> <span class="tok-type">NameList</span><span class="tok-punc">() {</span>\n' +
' <span class="tok-kw">const</span> names <span class="tok-punc">=</span> <span class="tok-punc">[</span><span class="tok-str">"Raj"</span><span class="tok-punc">,</span> <span class="tok-str">"Ravi"</span><span class="tok-punc">,</span> <span class="tok-str">"Sneha"</span><span class="tok-punc">];</span>\n\n' +
' <span class="tok-kw">return</span> <span class="tok-punc">(</span>\n' +
' <span class="tok-punc"><></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">h1</span><span class="tok-punc">></span>List of Names<span class="tok-punc"></</span><span class="tok-tag">h1</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">ul</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">{</span>names.map<span class="tok-punc">((</span>name<span class="tok-punc">,</span> index<span class="tok-punc">) => (</span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">li</span> <span class="lk-highlight" id="lkHighlight"><span class="tok-attr">key</span><span class="tok-punc">={</span>name<span class="tok-punc">}</span></span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">{</span>name<span class="tok-punc">}</span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">placeholder</span><span class="tok-punc">=</span><span class="tok-str">"note"</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"></</span><span class="tok-tag">li</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">))}</span>\n' +
' <span class="tok-punc"></</span><span class="tok-tag">ul</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"></></span>\n' +
' <span class="tok-punc">);</span>\n' +
'<span class="tok-punc">}</span>\n\n' +
'<span class="tok-kw">export</span> <span class="tok-kw">default</span> <span class="tok-type">NameList</span><span class="tok-punc">;</span>';
var codeEl = root.querySelector('#lkCode');
var statusEl = root.querySelector('#lkStatus');
var fixBtn = root.querySelector('#lkFixBtn');
var shuffleBtn = root.querySelector('#lkShuffleBtn');
var caption = root.querySelector('#lkCaption');
var listEl = root.querySelector('#lkList');
var isFixed = false;
var names = ['Raj', 'Ravi', 'Sneha'];
var order = names.slice();
var notesByPosition = ['', '', ''];
var notesByName = { Raj: '', Ravi: '', Sneha: '' };
codeEl.innerHTML = brokenCode;
function render(flashIndexes) {
listEl.innerHTML = '';
order.forEach(function (name, i) {
var li = document.createElement('li');
li.className = 'lk-item';
if (flashIndexes && flashIndexes.indexOf(i) !== -1) li.classList.add('lk-flash');
var label = document.createElement('span');
label.className = 'lk-item-name';
label.textContent = name;
var input = document.createElement('input');
input.type = 'text';
input.placeholder = 'note';
input.value = isFixed ? notesByName[name] : notesByPosition[i];
input.setAttribute('aria-label', 'note for ' + name);
(function (name, i) {
input.addEventListener('input', function (e) {
if (isFixed) notesByName[name] = e.target.value;
else notesByPosition[i] = e.target.value;
});
})(name, i);
li.appendChild(label);
li.appendChild(input);
listEl.appendChild(li);
});
if (flashIndexes && flashIndexes.length) {
setTimeout(function () {
listEl.querySelectorAll('.lk-flash').forEach(function (el) { el.classList.remove('lk-flash'); });
}, 900);
}
}
render();
shuffleBtn.addEventListener('click', function () {
var first = order.shift();
order.push(first);
var hadNotes = notesByPosition.some(function (v) { return v.trim() !== ''; }) ||
Object.keys(notesByName).some(function (k) { return notesByName[k].trim() !== ''; });
render(isFixed ? [] : [0, 1, 2]);
if (isFixed) {
caption.textContent = hadNotes
? 'β
Notes stayed attached to the right names β key={name} tracks identity.'
: 'Shuffled. Add a note first, then shuffle again to see it follow the name.';
} else {
caption.textContent = hadNotes
? 'π Look closely β the notes stayed in the same slot, not with the same name.'
: 'Shuffled. Add a note to a name first, then shuffle to see the bug.';
}
});
fixBtn.addEventListener('click', function () {
if (isFixed) return;
isFixed = true;
order.forEach(function (name, i) { notesByName[name] = notesByPosition[i]; });
codeEl.innerHTML = fixedCode;
var hl = root.querySelector('#lkHighlight');
if (hl) setTimeout(function () { hl.classList.add('is-settled'); }, 1600);
statusEl.textContent = 'key={name}';
statusEl.classList.add('is-fixed');
fixBtn.textContent = 'β
Using a stable key';
fixBtn.disabled = true;
render();
caption.textContent = 'Fixed β now shuffle again and watch each note stay with its name.';
});
})();
</script>β Explanation of the code:
namesArray: A simple list of names is stored in an array..map()Rendering: Themap()function loops through each name and returns a<li>element for it.keyProp: Each<li>has a uniquekey(here, the index) to help React identify elements efficiently.
π What to Avoid
Avoid using index as key if the list can be reordered or updated.
Donβt forget the
returnkeyword if using{}in arrow functions.{names.map((name, index) => { return <li key={index}>{name}</li>; })}