๐น What is useState with Arrays?
In React, useState can store arrays when you need to manage a list of data, such as:
Todo lists
Shopping carts
Users list
Dynamic UI elements
โ ๏ธ Arrays in state should never be mutated directly.
๐น Basic Syntax
const [items, setItems] = useState([]);itemsโ current arraysetItemsโ function to update arrayUpdating state replaces the old array
๐น Simple Example
import { useState } from "react";
function Fruits() {
const [fruits, setFruits] = useState(["Apple", "Banana"]);
return (
<div>
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
<button onClick={() => setFruits(["Apple", "Banana", "Orange"])}>
Add Orange
</button>
</div>
);
}๐น Adding Items to an Array
โ Correct Way (using spread operator)
setItems([...items, newItem]);Example
const addItem = () => {
setFruits([...fruits, "Mango"]);
};๐น Using Functional Update (Best Practice)
setItems(prevItems => [...prevItems, newItem]);โ Why?
Uses latest state
Avoids stale state bugs
Recommended for real apps
๐น Removing an Item from Array
const removeItem = (itemToRemove) => {
setItems(items.filter(item => item !== itemToRemove));
};Example
<button onClick={() => removeItem("Apple")}>
Remove Apple
</button>๐น Updating an Item in an Array
Example: Update by index
const updateItem = (index) => {
setItems(items.map((item, i) =>
i === index ? "Updated Item" : item
));
};๐น Updating Object Inside an Array
const [users, setUsers] = useState([
{ id: 1, name: "Alex" },
{ id: 2, name: "John" }
]);
const updateUser = (id) => {
setUsers(users.map(user =>
user.id === id ? { ...user, name: "Mike" } : user
));
};๐น Clearing the Array
setItems([]);๐น Common Mistakes
โ Mutating Array Directly
items.push("New Item");
setItems(items); // โ wrongโ Using Index as Key (Anti-pattern)
items.map((item, index) => <li key={index}>{item}</li>)โ๏ธ Use unique IDs instead
๐น Correct Way to Use Keys
items.map(item => <li key={item.id}>{item.name}</li>)๐น When to Use Array State?
โ Use when:
Managing dynamic lists
Rendering repeated components
โ Avoid when:
State becomes deeply nested โ split state or use reducer
<div class="infographic-todocrud" id="tc">
<style>
.infographic-todocrud {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--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-todocrud[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
}
.infographic-todocrud * { box-sizing: border-box; min-width: 0; }
.tc-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;
}
.tc-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.tc-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;
}
.tc-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.6rem; }
.tc-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 1.5rem; align-items: start; margin-bottom: 1.2rem; }
@media (max-width: 720px) { .tc-grid { grid-template-columns: 1fr; } }
/* ===== tabs ===== */
.tc-tabs { display: flex; justify-content: center; gap: 0.35rem; margin-bottom: 0.8rem; flex-wrap: wrap; }
.tc-tab {
font: inherit; font-size: 0.75rem; font-weight: 600; font-family: var(--mono-font);
background: var(--paper-alt); border: 1.5px solid var(--line); border-radius: 100px;
padding: 0.4rem 0.85rem; cursor: pointer; color: var(--ink-soft); transition: all 0.2s;
display: flex; align-items: center; gap: 0.35rem;
}
.tc-tab .tc-tab-num {
width: 15px; height: 15px; border-radius: 50%; background: var(--line); color: #fff;
font-size: 0.62rem; display: flex; align-items: center; justify-content: center; transition: background 0.2s;
}
.tc-tab:hover { border-color: var(--accent); color: var(--ink); }
.tc-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tc-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.tc-tab.is-active .tc-tab-num { background: rgba(255,255,255,0.3); }
/* ===== code panel ===== */
.tc-code-head { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.tc-code-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.tc-code-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.tc-code-block { border-radius: var(--radius); transition: box-shadow 0.25s ease; }
.tc-code-block.is-flash { box-shadow: 0 0 0 3px var(--accent); }
.tc-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem 0.7rem; overflow: auto; max-height: 300px;
}
.tc-code-block code { font-family: var(--mono-font); font-size: 0.72rem; line-height: 1.7; 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; }
.tc-panel { display: none; }
.tc-panel.is-visible { display: block; }
/* ===== output panel ===== */
.tc-output-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; }
.tc-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
.tc-preview {
border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; width: 100%; max-width: 320px;
box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18);
}
.tc-preview-bar { background: #e9e9e9; padding: 0.45rem 0.6rem; display: flex; gap: 0.3rem; }
.tc-preview-bar span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.tc-preview-bar span:nth-child(1) { background: #ff5f56; }
.tc-preview-bar span:nth-child(2) { background: #ffbd2e; }
.tc-preview-bar span:nth-child(3) { background: #27c93f; }
.tc-preview-page { background: #fff; padding: 1.2rem 1.1rem; font-family: Times, "Times New Roman", serif; color: #000; }
.tc-preview-page h2 { font-size: 1.15rem; margin: 0 0 0.7rem; }
.tc-input-row { display: flex; gap: 0.4rem; margin-bottom: 0.8rem; }
.tc-preview-page input[type="text"] {
flex: 1; font-family: inherit; font-size: 0.85rem; padding: 0.35rem 0.5rem; border: 1px solid #999; min-width: 0;
}
.tc-preview-page input[type="text"]:focus { outline: 2px solid var(--accent); border-color: var(--accent); }
.tc-add-btn {
font-family: sans-serif; font-size: 0.8rem; background: #efefef; border: 2px outset #ccc; padding: 0.35rem 0.7rem; cursor: pointer;
}
.tc-preview-page ul { list-style: none; margin: 0; padding: 0; }
.tc-preview-page li {
display: flex; align-items: center; gap: 0.4rem; padding: 0.35rem 0; border-bottom: 1px solid #eee; font-size: 0.88rem;
}
.tc-preview-page li span { flex: 1; word-break: break-word; }
.tc-preview-page li button {
font-family: sans-serif; font-size: 0.78rem; background: none; border: none; cursor: pointer; padding: 0.1rem 0.3rem;
}
.tc-empty { font-size: 0.8rem; color: #888; font-style: italic; padding: 0.4rem 0; }
.tc-caption {
text-align: center; font-size: 0.85rem; color: var(--ink-soft); min-height: 1.4em; max-width: 320px;
}
.tc-takeaway {
margin-top: 1.5rem; 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: 620px; margin-left: auto; margin-right: auto;
}
.tc-takeaway b { color: var(--accent-deep); }
@media (prefers-reduced-motion: reduce) {
.infographic-todocrud *, .infographic-todocrud *::before, .infographic-todocrud *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="tc-eyebrow">Full CRUD Example</div>
<h1 class="tc-title">A Todo List, Built With CRUD</h1>
<p class="tc-sub">It's live โ try the app, and the matching code lights up.</p>
<div class="tc-grid">
<div>
<div class="tc-tabs" id="tcTabs">
<button class="tc-tab is-active" data-tab="create" type="button"><span class="tc-tab-num">1</span> State</button>
<button class="tc-tab" data-tab="add" type="button"><span class="tc-tab-num">2</span> Add</button>
<button class="tc-tab" data-tab="update" type="button"><span class="tc-tab-num">3</span> Update</button>
<button class="tc-tab" data-tab="delete" type="button"><span class="tc-tab-num">4</span> Delete</button>
</div>
<div class="tc-panel is-visible" data-panel="create">
<div class="tc-code-block" id="tcBlockCreate">
<div class="tc-code-head"><span class="tc-code-name">Setting up state</span></div>
<pre><code><span class="tok-kw">import</span> <span class="tok-punc">{</span> <span class="tok-type">useState</span> <span class="tok-punc">}</span> <span class="tok-kw">from</span> <span class="tok-str">"react"</span><span class="tok-punc">;</span>
<span class="tok-kw">function</span> <span class="tok-type">TodoCRUD</span><span class="tok-punc">() {</span>
<span class="tok-kw">const</span> <span class="tok-punc">[</span>todos<span class="tok-punc">,</span> setTodos<span class="tok-punc">]</span> <span class="tok-punc">=</span> <span class="tok-type">useState</span><span class="tok-punc">([]);</span>
<span class="tok-kw">const</span> <span class="tok-punc">[</span>text<span class="tok-punc">,</span> setText<span class="tok-punc">]</span> <span class="tok-punc">=</span> <span class="tok-type">useState</span><span class="tok-punc">(</span><span class="tok-str">""</span><span class="tok-punc">);</span>
<span class="tok-kw">const</span> <span class="tok-punc">[</span>editId<span class="tok-punc">,</span> setEditId<span class="tok-punc">]</span> <span class="tok-punc">=</span> <span class="tok-type">useState</span><span class="tok-punc">(</span><span class="tok-kw">null</span><span class="tok-punc">);</span>
<span class="tok-comment">// ...add / update / delete below</span>
<span class="tok-punc">}</span></code></pre>
</div>
</div>
<div class="tc-panel" data-panel="add">
<div class="tc-code-block" id="tcBlockAdd">
<div class="tc-code-head"><span class="tc-code-name">Create a todo</span></div>
<pre><code><span class="tok-comment">// CREATE</span>
<span class="tok-kw">const</span> addTodo <span class="tok-punc">=</span> <span class="tok-punc">() => {</span>
<span class="tok-kw">if</span> <span class="tok-punc">(</span>text.trim<span class="tok-punc">() ===</span> <span class="tok-str">""</span><span class="tok-punc">)</span> <span class="tok-kw">return</span><span class="tok-punc">;</span>
setTodos<span class="tok-punc">(</span>prev <span class="tok-punc">=> [</span>
<span class="tok-punc">...</span>prev<span class="tok-punc">,</span>
<span class="tok-punc">{</span> <span class="tok-attr">id</span><span class="tok-punc">:</span> Date.now<span class="tok-punc">(),</span> text <span class="tok-punc">}</span>
<span class="tok-punc">]);</span>
setText<span class="tok-punc">(</span><span class="tok-str">""</span><span class="tok-punc">);</span>
<span class="tok-punc">};</span>
<span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">value</span><span class="tok-punc">={</span>text<span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={(</span>e<span class="tok-punc">) =></span> setText<span class="tok-punc">(</span>e.target.value<span class="tok-punc">)}</span> <span class="tok-punc">/></span>
<span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">onClick</span><span class="tok-punc">={</span>addTodo<span class="tok-punc">}></span>Add<span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span></code></pre>
</div>
</div>
<div class="tc-panel" data-panel="update">
<div class="tc-code-block" id="tcBlockUpdate">
<div class="tc-code-head"><span class="tc-code-name">Edit a todo</span></div>
<pre><code><span class="tok-comment">// UPDATE</span>
<span class="tok-kw">const</span> updateTodo <span class="tok-punc">=</span> <span class="tok-punc">() => {</span>
setTodos<span class="tok-punc">(</span>prev <span class="tok-punc">=></span>
prev.map<span class="tok-punc">(</span>todo <span class="tok-punc">=></span>
todo.id <span class="tok-punc">===</span> editId
<span class="tok-punc">? {</span> <span class="tok-punc">...</span>todo<span class="tok-punc">,</span> text <span class="tok-punc">}</span>
<span class="tok-punc">:</span> todo
<span class="tok-punc">)</span>
<span class="tok-punc">);</span>
setEditId<span class="tok-punc">(</span><span class="tok-kw">null</span><span class="tok-punc">);</span>
setText<span class="tok-punc">(</span><span class="tok-str">""</span><span class="tok-punc">);</span>
<span class="tok-punc">};</span>
<span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">onClick</span><span class="tok-punc">={() => {</span> setEditId<span class="tok-punc">(</span>todo.id<span class="tok-punc">);</span> setText<span class="tok-punc">(</span>todo.text<span class="tok-punc">);</span> <span class="tok-punc">}}></span>\u270f\ufe0f<span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span>
<span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">onClick</span><span class="tok-punc">={</span>updateTodo<span class="tok-punc">}></span>Update<span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span></code></pre>
</div>
</div>
<div class="tc-panel" data-panel="delete">
<div class="tc-code-block" id="tcBlockDelete">
<div class="tc-code-head"><span class="tc-code-name">Remove a todo</span></div>
<pre><code><span class="tok-comment">// DELETE</span>
<span class="tok-kw">const</span> deleteTodo <span class="tok-punc">=</span> <span class="tok-punc">(</span>id<span class="tok-punc">) => {</span>
setTodos<span class="tok-punc">(</span>prev <span class="tok-punc">=></span> prev.filter<span class="tok-punc">(</span>todo <span class="tok-punc">=></span> todo.id <span class="tok-punc">!==</span> id<span class="tok-punc">));</span>
<span class="tok-punc">};</span>
<span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">onClick</span><span class="tok-punc">={() =></span> deleteTodo<span class="tok-punc">(</span>todo.id<span class="tok-punc">)}></span>\u274c<span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span></code></pre>
</div>
</div>
</div>
<div class="tc-output-wrap">
<div class="tc-output-label">Output</div>
<div class="tc-preview">
<div class="tc-preview-bar"><span></span><span></span><span></span></div>
<div class="tc-preview-page">
<h2>Todo CRUD</h2>
<div class="tc-input-row">
<input type="text" id="tcInput" placeholder="Enter todo">
<button class="tc-add-btn" id="tcActionBtn" type="button">Add</button>
</div>
<ul id="tcList"></ul>
</div>
</div>
<p class="tc-caption" id="tcCaption">Try adding a todo below.</p>
</div>
</div>
<div class="tc-takeaway">Every button maps to <b>one function</b> โ Add calls <code style="font-family:var(--mono-font)">addTodo</code>, โ๏ธ pre-fills the form, Update calls <code style="font-family:var(--mono-font)">updateTodo</code>, โ calls <code style="font-family:var(--mono-font)">deleteTodo</code>.</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('tc');
if (!root) return;
/* --- dark mode detection: body attribute > cookie > postMessage > light --- */
function applyDark(dark) {
if (dark) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
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;
}
applyDark(dark);
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') applyDark(!!e.data.value);
});
/* --- tabs --- */
var tabs = root.querySelectorAll('.tc-tab');
var panels = root.querySelectorAll('.tc-panel');
var flashTimer = null;
function switchTab(id, opts) {
tabs.forEach(function (t) { t.classList.toggle('is-active', t.getAttribute('data-tab') === id); });
panels.forEach(function (p) { p.classList.toggle('is-visible', p.getAttribute('data-panel') === id); });
if (opts && opts.flash) {
var block = root.querySelector('#tcBlock' + id.charAt(0).toUpperCase() + id.slice(1));
if (block) {
block.classList.remove('is-flash');
void block.offsetWidth;
block.classList.add('is-flash');
clearTimeout(flashTimer);
flashTimer = setTimeout(function () { block.classList.remove('is-flash'); }, 700);
}
}
}
tabs.forEach(function (t) {
t.addEventListener('click', function () { switchTab(t.getAttribute('data-tab')); });
});
/* --- live CRUD simulation --- */
var inputEl = root.querySelector('#tcInput');
var actionBtn = root.querySelector('#tcActionBtn');
var listEl = root.querySelector('#tcList');
var caption = root.querySelector('#tcCaption');
var todos = [];
var text = '';
var editId = null;
function render() {
actionBtn.textContent = editId !== null ? 'Update' : 'Add';
inputEl.value = text;
listEl.innerHTML = '';
if (todos.length === 0) {
var empty = document.createElement('li');
empty.className = 'tc-empty';
empty.textContent = 'No todos yet';
listEl.appendChild(empty);
return;
}
todos.forEach(function (todo) {
var li = document.createElement('li');
var span = document.createElement('span');
span.textContent = todo.text;
var editBtn = document.createElement('button');
editBtn.type = 'button';
editBtn.textContent = '\u270f\ufe0f';
editBtn.addEventListener('click', function () { startEdit(todo.id, todo.text); });
var delBtn = document.createElement('button');
delBtn.type = 'button';
delBtn.textContent = '\u274c';
delBtn.addEventListener('click', function () { deleteTodo(todo.id); });
li.appendChild(span);
li.appendChild(editBtn);
li.appendChild(delBtn);
listEl.appendChild(li);
});
}
function addTodo() {
if (text.trim() === '') return;
todos = todos.concat([{ id: Date.now(), text: text }]);
text = '';
caption.textContent = 'addTodo() ran \u2014 pushed a new todo onto the list.';
switchTab('add', { flash: true });
render();
}
function updateTodo() {
todos = todos.map(function (t) {
return t.id === editId ? Object.assign({}, t, { text: text }) : t;
});
editId = null;
text = '';
caption.textContent = 'updateTodo() ran \u2014 replaced just that one todo.';
switchTab('update', { flash: true });
render();
}
function deleteTodo(id) {
todos = todos.filter(function (t) { return t.id !== id; });
caption.textContent = 'deleteTodo() ran \u2014 filtered that todo out.';
switchTab('delete', { flash: true });
render();
}
function startEdit(id, existingText) {
editId = id;
text = existingText;
caption.textContent = 'Edit clicked \u2014 editId and text are pre-filled.';
switchTab('update', { flash: true });
render();
}
inputEl.addEventListener('input', function () { text = inputEl.value; });
actionBtn.addEventListener('click', function () {
if (editId !== null) updateTodo(); else addTodo();
});
render();
})();
</script>๐น State Used
State | Purpose |
|---|---|
| Stores todo list |
| Input value |
| Tracks which todo is being edited |
๐ง Key Concepts Used
useStatewith array of objectsControlled inputs
Conditional rendering
map()&filter()Immutable state updates