▶️ Live demo
Try it yourself — interact with the example below.
Loading demo…
🎯 What Is Deleting Items?
Imagine you have a shopping list on the fridge:
WITHOUT PROPER DELETE (The Mess):
┌─────────────────────────────────────────┐
│ 🏠 THE KITCHEN (Your App) │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 📋 SHOPPING LIST (State) │ │
│ │ • Milk │ │
│ │ • Eggs │ │
│ │ • Bread │ │
│ │ • Socks (wrong item!) │ │
│ │ │ │
│ │ "I want to cross off Socks!" │ │
│ │ But the marker is in the │ │
│ │ LIVING ROOM (Parent) │ │
│ │ │ │
│ │ ❌ Can't reach it! │ │
│ └─────────────────────────────────┘ │
│ │
│ You (Item) can't change the list! │
│ Only Mom (App) can change the list! │
└─────────────────────────────────────────┘
WITH PROPER DELETE (The Fix):
┌─────────────────────────────────────────┐
│ 🏠 THE KITCHEN (Your App) │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 👩 MOM (App Component) │ │
│ │ "I own the shopping list!" │ │
│ │ │ │
│ │ 📞 Gives you a phone: │ │
│ │ "Call me with the item number │ │
│ │ and I'll cross it off!" │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 👶 YOU (Item Component) │ │
│ │ "Item #3 is Socks!" │ │
│ │ │ │
│ │ 📞 Call Mom: "Delete #3!" │ │
│ │ (onClick={() => onDelete(3)}) │ │
│ │ │ │
│ │ Mom crosses it off! │ │
│ │ List updates! │ │
│ └─────────────────────────────────┘ │
│ │
│ ✅ You tell Mom WHICH item! │
│ ✅ Mom updates the list! │
│ ✅ Fridge shows updated list! │
└─────────────────────────────────────────┘
THE DELETE ITEM DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│ Need to delete an item? │
│ ↓ │
│ State lives in Parent (lifted up) │
│ ↓ │
│ Click happens in Child (Item) │
│ ↓ │
│ Child needs to tell Parent WHICH item │
│ → Pass ID as argument! │
│ → onClick={() => onDeleteItem(id)} │
│ → NOT onClick={onDeleteItem}! │
│ → (That passes the event, not ID!) │
│ ↓ │
│ Parent filters array: │
│ → items.filter(item => item.id !== id) │
│ → Keeps everything EXCEPT that item! │
│ → Returns NEW array (immutable!) │
│ → React re-renders! Everyone syncs! │
└─────────────────────────────────────────┘
⚠️ The Big Problem: "The Event Object Bug"
// ==========================================
// THE "DELETE BUTTON DOES NOTHING" TRAP
// ==========================================
// ❌ WRONG: Passing the function reference directly
function BadApp() {
const [items, setItems] = useState([
{ id: 1, description: "Passports", packed: false },
{ id: 2, description: "Socks", packed: false },
{ id: 3, description: "Charger", packed: true },
]);
function handleDeleteItem(id) {
console.log("Deleting item with id:", id); // ← Logs the EVENT object! 😱
setItems(prev => prev.filter(item => item.id !== id));
}
return (
<div className="app">
<PackingList items={items} onDeleteItem={handleDeleteItem} />
</div>
);
}
function PackingList({ items, onDeleteItem }) {
return (
<div className="list">
<ul>
{items.map(item => (
<Item
item={item}
key={item.id}
onDeleteItem={onDeleteItem} // ← Passing through...
/>
))}
</ul>
</div>
);
}
function Item({ item, onDeleteItem }) {
return (
<li>
<span>{item.quantity} {item.description}</span>
{/* ❌ WRONG: This passes the EVENT, not the ID! */}
<button onClick={onDeleteItem}>❌</button>
{/*
What React sees:
onClick={onDeleteItem}
When clicked, React calls:
onDeleteItem(event) ← The click event object!
So id = [object MouseEvent]
NOT the item.id!
filter tries to compare:
item.id !== [object MouseEvent] ← Always true!
Nothing gets deleted! 😢
*/}
</li>
);
}
// Problems:
// 1. Button click does NOTHING!
// 2. Console shows a MouseEvent object, not an ID!
// 3. filter() compares against event object, not number!
// 4. Every item stays in the array!
// 5. User is frustrated! "Why won't it delete?!"
// ==========================================
// THE SOLUTION: Wrap in Arrow Function!
// ==========================================
// ✅ CORRECT: Arrow function passes the ID!
function GoodApp() {
const [items, setItems] = useState([]);
function handleDeleteItem(id) {
console.log("Deleting item with id:", id); // ← Logs: 2 ✓
setItems(prev => prev.filter(item => item.id !== id));
}
return (
<div className="app">
<PackingList items={items} onDeleteItem={handleDeleteItem} />
</div>
);
}
function PackingList({ items, onDeleteItem }) {
return (
<div className="list">
<ul>
{items.map(item => (
<Item
item={item}
key={item.id}
onDeleteItem={onDeleteItem}
/>
))}
</ul>
</div>
);
}
function Item({ item, onDeleteItem }) {
return (
<li>
<span style={item.packed ? { textDecoration: "line-through" } : {}}>
{item.quantity} {item.description}
</span>
{/* ✅ CORRECT: Arrow function wraps the call! */}
<button onClick={() => onDeleteItem(item.id)}>❌</button>
{/*
What React sees:
onClick={() => onDeleteItem(item.id)}
When clicked, React calls:
() => onDeleteItem(2) ← The arrow function!
Which then calls:
onDeleteItem(2) ← With the actual ID!
filter compares:
item.id !== 2 ← Works correctly!
Item with id 2 is removed! ✓
*/}
</li>
);
}
// What happens when user clicks ❌ on "Socks" (id: 2):
//
// 1. User clicks the delete button on Item component
// 2. React calls the arrow function: () => onDeleteItem(2)
// 3. Arrow function calls onDeleteItem(2) with the ID!
// 4. App's handleDeleteItem(2) runs
// 5. setItems filters out item with id 2
// 6. App re-renders with new items array
// 7. PackingList receives new items prop
// 8. Item with "Socks" is GONE from the UI! ✓
// 9. Console shows: "Deleting item with id: 2" ✓
//
// Why this works:
// → Arrow function captures the item.id at render time!
// → When clicked, it passes that captured ID!
// → Parent knows exactly WHICH item to delete!
// → filter() creates new array without that item!
// → React sees state change and re-renders!
📋 Complete Visual Examples
Create file: deleting-items.js
// ==========================================
// DELETING ITEMS - Complete Guide
// ==========================================
/*
THE DELETE ITEM DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│ Need to delete from a list? │
│ ↓ │
│ State lives in Parent (lifted up) │
│ ↓ │
│ Delete button in Child (Item) │
│ ↓ │
│ Need to pass WHICH item to delete? │
│ → YES → Use arrow function! │
│ → onClick={() => onDelete(id)} │
│ → NOT onClick={onDelete} │
│ → (That sends the click event!) │
│ ↓ │
│ Parent receives the ID │
│ → filter(item => item.id !== id) │
│ → Returns NEW array (immutable!) │
│ → setItems(newArray) │
│ → React re-renders! │
└─────────────────────────────────────────┘
THE EVENT OBJECT TRAP:
┌─────────────────────────────────────────┐
│ ❌ WRONG: onClick={handleDelete} │
│ → React calls: handleDelete(event) │
│ → event = MouseEvent object │
│ → Your function expects an ID! │
│ → ID is now an event object! │
│ → filter breaks! Nothing deletes! │
│ │
│ ✅ CORRECT: onClick={() => handleDelete(id)} │
│ → React calls: () => handleDelete(2) │
│ → Arrow function runs │
│ → handleDelete(2) with real ID! │
│ → filter works! Item deletes! │
└─────────────────────────────────────────┘
*/
// ==========================================
// EXAMPLE 1: The Event Object Trap
// ==========================================
import { useState } from 'react';
function EventTrapDemo() {
const [items, setItems] = useState([
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
]);
function handleDelete(id) {
console.log("Received:", id); // ← What do we get?
console.log("Type:", typeof id); // ← object? or number?
setItems(prev => prev.filter(item => item.id !== id));
}
return (
<div>
<h3>The Event Object Trap 🪤</h3>
{items.map(item => (
<div key={item.id}>
<span>{item.name}</span>
{/* ❌ WRONG: Passes the EVENT object! */}
<button onClick={handleDelete}>
Delete (Broken)
</button>
{/*
Click logs:
Received: SyntheticBaseEvent { ... }
Type: "object"
filter compares: 1 !== [object] → true (keeps it!)
NOTHING DELETES!
*/}
{/* ✅ CORRECT: Passes the ID! */}
<button onClick={() => handleDelete(item.id)}>
Delete (Works)
</button>
{/*
Click logs:
Received: 1
Type: "number"
filter compares: 1 !== 1 → false (removes it!)
ITEM DELETES!
*/}
</div>
))}
</div>
);
}
// Visual Flow of the TRAP:
// Initial: items = [{id: 1, name: "Apple"}, {id: 2, name: "Banana"}]
//
// User clicks "Delete (Broken)" on Apple:
// React calls: handleDelete(event)
// event = SyntheticBaseEvent {type: "click", ...}
// id = [object Object] (the event!)
// filter: item.id !== [object] → ALWAYS true!
// Result: [{Apple}, {Banana}] ← Nothing changed! 😢
//
// User clicks "Delete (Works)" on Apple:
// React calls: () => handleDelete(1)
// Arrow function runs → handleDelete(1)
// id = 1 (the number!)
// filter: item.id !== 1 → false for Apple!
// Result: [{Banana}] ← Apple removed! ✓
// ==========================================
// EXAMPLE 2: Passing Functions Through the Tree
// ==========================================
function App() {
// 🏠 HOME: App owns the items state
const [items, setItems] = useState([
{ id: 1, description: "Passports", quantity: 2, packed: false },
{ id: 2, description: "Socks", quantity: 6, packed: false },
{ id: 3, description: "Charger", quantity: 1, packed: true },
]);
// Function to delete items lives here!
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
return (
<div className="app">
<Logo />
<Form />
{/* Pass the delete function down! */}
<PackingList items={items} onDeleteItem={handleDeleteItem} />
<Stats />
</div>
);
}
// PackingList receives the function and passes it deeper!
function PackingList({ items, onDeleteItem }) {
return (
<div className="list">
<ul>
{items.map(item => (
/* Pass function to each Item */
<Item
item={item}
key={item.id}
onDeleteItem={onDeleteItem} // ← Passing through!
/>
))}
</ul>
</div>
);
}
// Item receives the function and uses it!
function Item({ item, onDeleteItem }) {
return (
<li>
<input type="checkbox" checked={item.packed} />
<span style={item.packed ? { textDecoration: "line-through" } : {}}>
{item.quantity} {item.description}
</span>
{/* Call with the specific item's ID! */}
<button onClick={() => onDeleteItem(item.id)}>❌</button>
</li>
);
}
// Visual Flow:
// 1. App owns items array
// 2. App passes onDeleteItem={handleDeleteItem} to PackingList
// 3. PackingList passes onDeleteItem={onDeleteItem} to Item
// 4. Item stores onDeleteItem in its props
// 5. User clicks ❌ on "Socks" (id: 2)
// 6. React calls: () => onDeleteItem(2)
// 7. onDeleteItem(2) → App's handleDeleteItem(2)
// 8. App filters out item with id 2
// 9. App re-renders
// 10. PackingList receives new items (without Socks)
// 11. Item with id 2 is gone from UI! ✓
//
// Key insight: The function flows DOWN through 2 levels!
// The ID travels UP through the function call!
// ==========================================
// EXAMPLE 3: The filter() Method (Immutable Delete)
// ==========================================
function FilterDemo() {
const items = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Orange" },
];
// ❌ WRONG: Mutating the array!
// items.splice(1, 1); ← NEVER do this in React!
// items.pop(); ← NEVER do this in React!
// delete items[1]; ← NEVER do this in React!
// ✅ CORRECT: Create new array with filter!
const idToDelete = 2;
const newItems = items.filter(item => item.id !== idToDelete);
// Result: [{id: 1, name: "Apple"}, {id: 3, name: "Orange"}]
// Banana is gone! But original array is untouched!
// How filter works:
// Array: [{Apple}, {Banana}, {Orange}]
// ↓ ↓ ↓
// Test: 1 !== 2 2 !== 2 3 !== 2
// Result: true false true
// Keep? ✓ ✗ ✓
// New: [{Apple}, {Orange}]
//
// Banana is filtered OUT!
return (
<div>
<h3>filter() Visual</h3>
<p>Original: {items.map(i => i.name).join(', ')}</p>
<p>After filter(id !== 2): {newItems.map(i => i.name).join(', ')}</p>
</div>
);
}
// ==========================================
// EXAMPLE 4: The Complete Delete Pattern
// ==========================================
function CompleteDeletePattern() {
const [items, setItems] = useState([
{ id: 1, description: "Passports", quantity: 2, packed: false },
{ id: 2, description: "Socks", quantity: 6, packed: false },
{ id: 3, description: "Charger", quantity: 1, packed: true },
]);
// DELETE handler
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
// TOGGLE handler (bonus!)
function handleToggleItem(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
}
return (
<div>
<PackingList
items={items}
onDeleteItem={handleDeleteItem}
onToggleItem={handleToggleItem}
/>
</div>
);
}
function PackingList({ items, onDeleteItem, onToggleItem }) {
return (
<ul>
{items.map(item => (
<Item
key={item.id}
item={item}
onDeleteItem={onDeleteItem}
onToggleItem={onToggleItem}
/>
))}
</ul>
);
}
function Item({ item, onDeleteItem, onToggleItem }) {
return (
<li>
{/* Toggle: Also needs arrow function! */}
<input
type="checkbox"
checked={item.packed}
onChange={() => onToggleItem(item.id)} // ← Arrow function!
/>
<span style={item.packed ? { textDecoration: "line-through" } : {}}>
{item.quantity} {item.description}
</span>
{/* Delete: Arrow function with ID! */}
<button onClick={() => onDeleteItem(item.id)}>❌</button>
</li>
);
}
// ==========================================
// EXAMPLE 5: The Naming Convention
// ==========================================
function NamingConventionDemo() {
const [items, setItems] = useState([]);
// In parent: handle + Action + Item
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
function handleAddItem(item) {
setItems(prev => [...prev, item]);
}
return (
<div>
{/*
Prop naming convention:
on + Action + Item = onDeleteItem, onAddItem
This matches the handler name!
*/}
<Form onAddItem={handleAddItem} />
<List onDeleteItem={handleDeleteItem} />
</div>
);
}
function List({ onDeleteItem }) {
return (
<div>
{/*
When passing to deeper children:
Keep the same prop name for clarity!
*/}
<Item onDeleteItem={onDeleteItem} />
</div>
);
}
function Item({ onDeleteItem }) {
const id = 1;
return (
<button onClick={() => onDeleteItem(id)}>
Delete
</button>
);
}
🚀 Interactive React Usage Examples
Complete React File: DeletingItemsMasterClass.jsx
import React, { useState } from 'react';
// ==========================================
// INTERACTIVE DELETING ITEMS DEMO
// ==========================================
function DeletingItemsMasterClass() {
const [activeDemo, setActiveDemo] = useState('flowchart');
const demos = {
flowchart: { title: 'Decision Flowchart', component: <FlowchartDemo /> },
eventtrap: { title: 'The Event Trap', component: <EventTrapDemo /> },
filter: { title: 'filter() Visual', component: <FilterVisualDemo /> },
propdrill: { title: 'Passing Down', component: <PropDrillDemo /> },
complete: { title: 'Complete App', component: <CompleteAppDemo /> }
};
return (
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<header style={{ background: '#264653', color: 'white', padding: '30px', borderRadius: '10px', marginBottom: '30px' }}>
<h1 style={{ margin: 0 }}>🗑️ Deleting Items</h1>
<p style={{ margin: '10px 0 0 0', opacity: 0.9 }}>The Event Object Trap & filter()</p>
</header>
<nav style={{ display: 'flex', gap: '10px', marginBottom: '30px', flexWrap: 'wrap' }}>
{Object.entries(demos).map(([key, { title }]) => (
<button
key={key}
onClick={() => setActiveDemo(key)}
style={{
padding: '12px 24px',
fontSize: '16px',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
backgroundColor: activeDemo === key ? '#e76f51' : '#e0e0e0',
color: activeDemo === key ? 'white' : '#333',
fontWeight: activeDemo === key ? 'bold' : 'normal'
}}
>
{title}
</button>
))}
</nav>
<main style={{ background: '#f8f9fa', padding: '30px', borderRadius: '10px', minHeight: '400px' }}>
{demos[activeDemo].component}
</main>
</div>
);
}
// ==========================================
// DEMO 1: The Decision Flowchart
// ==========================================
function FlowchartDemo() {
const [step, setStep] = useState(1);
const steps = [
{
num: 1,
title: 'State in Parent?',
question: 'Is the items array in the parent component?',
code: 'const [items, setItems] = useState([]); // In App',
visual: '⬆️',
desc: 'State must be lifted to common parent first!'
},
{
num: 2,
title: 'Pass Function Down',
question: 'Did you pass the delete function to children?',
code: '<PackingList onDeleteItem={handleDeleteItem} />',
visual: '⬇️',
desc: 'Function flows down through props!'
},
{
num: 3,
title: 'Pass to Item',
question: 'Did you pass it through to the Item component?',
code: '<Item onDeleteItem={onDeleteItem} />',
visual: '⬇️',
desc: 'Drill the prop down to where the button lives!'
},
{
num: 4,
title: 'Arrow Function?',
question: 'Did you wrap the call in an arrow function?',
code: 'onClick={() => onDeleteItem(item.id)}',
visual: '⚠️',
desc: 'CRITICAL! Without arrow, event object is passed!'
},
{
num: 5,
title: 'filter() Array',
question: 'Does parent use filter to remove item?',
code: 'setItems(prev => prev.filter(i => i.id !== id))',
visual: '🧹',
desc: 'Creates NEW array without the deleted item!'
},
{
num: 6,
title: 'Re-render!',
question: 'Does React update the UI?',
code: '// Item disappears! Everyone syncs!',
visual: '✨',
desc: 'React sees state change and re-renders!'
}
];
const current = steps[step - 1];
return (
<div>
<h2>The Delete Item Flowchart 🧭</h2>
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px', justifyContent: 'center' }}>
{steps.map(s => (
<button
key={s.num}
onClick={() => setStep(s.num)}
style={{
width: '60px',
height: '60px',
borderRadius: '50%',
border: 'none',
cursor: 'pointer',
background: step >= s.num ? '#e76f51' : '#e0e0e0',
color: step >= s.num ? 'white' : '#333',
fontSize: '24px',
fontWeight: 'bold'
}}
>
{s.num}
</button>
))}
</div>
<div style={{ padding: '30px', background: 'white', borderRadius: '10px', border: '2px solid #264653' }}>
<h3 style={{ color: '#264653', marginTop: 0 }}>Step {current.num}: {current.title}</h3>
<p style={{ fontSize: '20px', color: '#555', fontWeight: 'bold' }}>{current.question}</p>
<div style={{ display: 'grid', gap: '20px', gridTemplateColumns: '1fr 1fr', marginBottom: '20px' }}>
<div>
<pre style={{
background: '#1e1e1e',
color: '#d4d4d4',
padding: '20px',
borderRadius: '8px',
fontSize: '16px'
}}>
{current.code}
</pre>
<p style={{ fontSize: '18px', color: '#555' }}>{current.desc}</p>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '80px' }}>
{current.visual}
</div>
</div>
</div>
<div style={{ marginTop: '20px', padding: '15px', background: '#fff3e0', borderRadius: '8px' }}>
<h4>🧠 Memory Trick:</h4>
<p>"Always wrap your delete in an arrow, or the event object will give you a scare!"</p>
</div>
</div>
);
}
// ==========================================
// DEMO 2: The Event Trap
// ==========================================
function EventTrapDemo() {
const [items, setItems] = useState([
{ id: 1, name: "Apple 🍎" },
{ id: 2, name: "Banana 🍌" },
{ id: 3, name: "Orange 🍊" },
]);
const [lastLog, setLastLog] = useState("");
function handleDelete(id) {
const log = `Received: ${id} (type: ${typeof id})`;
setLastLog(log);
if (typeof id === 'number') {
setItems(prev => prev.filter(item => item.id !== id));
}
}
return (
<div>
<h2>The Event Object Trap 🪤</h2>
<div style={{ marginBottom: '20px', padding: '15px', background: '#ffebee', borderRadius: '8px' }}>
<h4>Click both buttons and see what happens! The broken one passes the event object!</h4>
</div>
{lastLog && (
<div style={{ padding: '15px', background: '#e3f2fd', borderRadius: '8px', marginBottom: '20px', fontFamily: 'monospace' }}>
{lastLog}
</div>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
{items.map(item => (
<div key={item.id} style={{
padding: '20px',
background: 'white',
borderRadius: '10px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<span style={{ fontSize: '20px' }}>{item.name}</span>
<div style={{ display: 'flex', gap: '10px' }}>
<button
onClick={handleDelete}
style={{ padding: '10px 20px', background: '#ffcdd2', color: '#c62828', border: '1px solid #c62828', borderRadius: '5px' }}
>
❌ Broken (no arrow)
</button>
<button
onClick={() => handleDelete(item.id)}
style={{ padding: '10px 20px', background: '#c8e6c9', color: '#2e7d32', border: '1px solid #2e7d32', borderRadius: '5px' }}
>
✅ Works (arrow)
</button>
</div>
</div>
))}
</div>
<div style={{ marginTop: '20px', padding: '15px', background: '#fff3e0', borderRadius: '8px' }}>
<h4>🧠 The Difference:</h4>
<div style={{ fontFamily: 'monospace', fontSize: '14px', lineHeight: '2' }}>
<span style={{ color: '#c62828' }}>❌ Broken:</span><br/>
onClick={'{handleDelete}'}<br/>
// React calls: handleDelete(event)<br/>
// id = SyntheticBaseEvent {'{...}'}<br/>
// filter compares number !== object → true → KEEPS it!<br/>
<br/>
<span style={{ color: '#2e7d32' }}>✅ Works:</span><br/>
onClick={'{() => handleDelete(item.id)}'}<br/>
// React calls: () ={'>'} handleDelete(2)<br/>
// id = 2 (number!)<br/>
// filter compares 3 !== 2 → true → KEEPS it!<br/>
// filter compares 2 !== 2 → false → REMOVES it!
</div>
</div>
</div>
);
}
// ==========================================
// DEMO 3: filter() Visual
// ==========================================
function FilterVisualDemo() {
const [items, setItems] = useState([
{ id: 1, name: "Apple", emoji: "🍎" },
{ id: 2, name: "Banana", emoji: "🍌" },
{ id: 3, name: "Orange", emoji: "🍊" },
{ id: 4, name: "Grape", emoji: "🍇" },
]);
const [deletedId, setDeletedId] = useState(null);
function handleDelete(id) {
setDeletedId(id);
setTimeout(() => {
setItems(prev => prev.filter(item => item.id !== id));
setDeletedId(null);
}, 1000);
}
return (
<div>
<h2>filter() Visual 🧹</h2>
<div style={{ marginBottom: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
<h4>Click an item to see how filter() removes it while keeping others!</h4>
</div>
<div style={{ display: 'flex', gap: '10px', justifyContent: 'center', marginBottom: '20px' }}>
{items.map(item => (
<button
key={item.id}
onClick={() => handleDelete(item.id)}
style={{
padding: '20px',
fontSize: '30px',
border: '2px solid #264653',
borderRadius: '10px',
cursor: 'pointer',
background: deletedId === item.id ? '#ffcdd2' : 'white',
transform: deletedId === item.id ? 'scale(0.8)' : 'scale(1)',
transition: 'all 0.3s'
}}
>
{item.emoji}
<div style={{ fontSize: '12px', marginTop: '5px' }}>id: {item.id}</div>
</button>
))}
</div>
<div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
<h4>How filter(item.id !== {deletedId || '?'}) works:</h4>
<div style={{ fontFamily: 'monospace', fontSize: '14px', lineHeight: '2' }}>
{items.map(item => {
const willKeep = item.id !== deletedId;
return (
<div key={item.id} style={{
color: willKeep ? '#2e7d32' : '#c62828',
textDecoration: willKeep ? 'none' : 'line-through'
}}>
{item.id} !== {deletedId || '?'} → {willKeep.toString()} → {willKeep ? 'KEEP ✓' : 'REMOVE ✗'}
</div>
);
})}
</div>
</div>
<div style={{ marginTop: '20px', padding: '15px', background: '#e8f5e9', borderRadius: '8px' }}>
<h4>🧠 filter() Rule:</h4>
<p>Keep items where the condition is TRUE. Remove items where the condition is FALSE.</p>
<code>items.filter(item ={'>'} item.id !== idToDelete)</code>
</div>
</div>
);
}
// ==========================================
// DEMO 4: Passing Functions Down
// ==========================================
function PropDrillDemo() {
const [flow, setFlow] = useState([]);
function addToFlow(message) {
setFlow(prev => [...prev, message]);
}
function resetFlow() {
setFlow([]);
}
return (
<div>
<h2>Passing Functions Down 📞</h2>
<div style={{ marginBottom: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
<h4>Watch how the function travels from App → List → Item!</h4>
</div>
<div style={{ padding: '20px', background: '#264653', color: 'white', borderRadius: '10px', marginBottom: '20px' }}>
<h3 style={{ margin: '0 0 10px 0' }}>🏠 App Component</h3>
<p style={{ margin: 0, fontSize: '14px' }}>Owns handleDeleteItem function</p>
</div>
<div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
<DrillList onAction={addToFlow} onReset={resetFlow} />
</div>
<div style={{ marginTop: '20px', padding: '15px', background: '#f5f5f5', borderRadius: '8px', fontFamily: 'monospace', fontSize: '12px' }}>
<h4>Flow Log:</h4>
{flow.length === 0 ? <p style={{ color: '#999' }}>Click a button to see the flow...</p> : (
<ol>
{flow.map((msg, i) => <li key={i}>{msg}</li>)}
</ol>
)}
</div>
</div>
);
}
function DrillList({ onAction, onReset }) {
return (
<div>
<div style={{ padding: '15px', background: '#e8f5e9', borderRadius: '8px', marginBottom: '15px' }}>
<h4 style={{ margin: 0 }}>📋 List Component</h4>
<p style={{ margin: '5px 0 0 0', fontSize: '14px', color: '#666' }}>Receives onAction, passes to Item</p>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<DrillItem label="Item A" id={1} onAction={onAction} />
<DrillItem label="Item B" id={2} onAction={onAction} />
</div>
<button
onClick={onReset}
style={{ marginTop: '15px', padding: '8px 16px', background: '#e76f51', color: 'white', border: 'none', borderRadius: '5px' }}
>
Reset Log
</button>
</div>
);
}
function DrillItem({ label, id, onAction }) {
return (
<button
onClick={() => onAction(`Item ${id} clicked! Function traveled: App → List → Item → back to App!`)}
style={{ padding: '15px 30px', background: '#2a9d8f', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
>
{label}
</button>
);
}
// ==========================================
// DEMO 5: Complete Far Away App
// ==========================================
function CompleteAppDemo() {
const [items, setItems] = useState([
{ id: 1, description: "Passports", quantity: 2, packed: false },
{ id: 2, description: "Socks", quantity: 6, packed: false },
{ id: 3, description: "Charger", quantity: 1, packed: true },
]);
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
function handleToggleItem(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
}
function handleAddItem(item) {
setItems(prev => [...prev, item]);
}
const numItems = items.length;
const numPacked = items.filter(item => item.packed).length;
const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;
return (
<div>
<h2>Complete Far Away App 🌴</h2>
<div style={{ marginBottom: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
<h4>Full CRUD: Create (Form), Read (List), Update (Toggle), Delete (❌)!</h4>
</div>
<div style={{
padding: '30px',
background: 'white',
borderRadius: '10px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)'
}}>
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
<h1 style={{ color: '#264653' }}>🌴 Far Away 💼</h1>
</div>
<CompleteForm onAddItem={handleAddItem} />
<div style={{ marginTop: '20px' }}>
<CompletePackingList
items={items}
onDeleteItem={handleDeleteItem}
onToggleItem={handleToggleItem}
/>
</div>
<div style={{ marginTop: '20px', padding: '15px', background: '#264653', color: 'white', borderRadius: '8px', textAlign: 'center' }}>
<em>
{percentage === 100
? "You got everything! Ready to go! ✈️"
: `💼 You have ${numItems} items, packed ${numPacked} (${percentage}%)`
}
</em>
</div>
</div>
</div>
);
}
function CompleteForm({ onAddItem }) {
const [description, setDescription] = useState("");
const [quantity, setQuantity] = useState(1);
function handleSubmit(e) {
e.preventDefault();
if (!description) return;
onAddItem({
description,
quantity,
packed: false,
id: Date.now()
});
setDescription("");
setQuantity(1);
}
return (
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: '10px', justifyContent: 'center' }}>
<select
value={quantity}
onChange={e => setQuantity(Number(e.target.value))}
style={{ padding: '10px', borderRadius: '5px' }}
>
{Array.from({ length: 10 }, (_, i) => i + 1).map(num => (
<option key={num} value={num}>{num}</option>
))}
</select>
<input
type="text"
placeholder="Item..."
value={description}
onChange={e => setDescription(e.target.value)}
style={{ padding: '10px', borderRadius: '5px', border: '1px solid #ddd', width: '200px' }}
/>
<button style={{ padding: '10px 20px', background: '#2a9d8f', color: 'white', border: 'none', borderRadius: '5px' }}>
Add
</button>
</form>
);
}
function CompletePackingList({ items, onDeleteItem, onToggleItem }) {
return (
<div style={{ background: '#f8f9fa', padding: '20px', borderRadius: '8px' }}>
{items.length === 0 ? (
<p style={{ color: '#999', textAlign: 'center' }}>Start adding items for your trip! 🌴</p>
) : (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map(item => (
<li key={item.id} style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
padding: '10px',
borderBottom: '1px solid #eee',
background: item.packed ? '#e8f5e9' : 'white',
borderRadius: '5px',
marginBottom: '5px'
}}>
<input
type="checkbox"
checked={item.packed}
onChange={() => onToggleItem(item.id)}
/>
<span style={{
flex: 1,
textDecoration: item.packed ? 'line-through' : 'none',
color: item.packed ? '#999' : '#333'
}}>
{item.quantity} {item.description}
</span>
<button
onClick={() => onDeleteItem(item.id)}
style={{ background: '#e76f51', color: 'white', border: 'none', borderRadius: '3px', cursor: 'pointer' }}
>
❌
</button>
</li>
))}
</ul>
)}
</div>
);
}
export default DeletingItemsMasterClass;
🧠 Memory Aids for Poor Logic Thinking
The "Library Book Return" Analogy
┌─────────────────────────────────────────────────┐
│ DELETING ITEMS = RETURNING A LIBRARY BOOK │
│ │
│ Scenario: You want to return a book. │
│ │
│ ❌ WRONG WAY (Tossing it anywhere): │
│ ┌──────────┐ │
│ │ You have │ │
│ │ book #42 │ │
│ │ │ │
│ │ "I'll just │ │
│ │ toss it │ │
│ │ here!" │ │
│ │ │ │
│ │ *throws │ │
│ │ book* │ │
│ │ │ │
│ │ ❌ Librarian doesn't know which book! │
│ │ ❌ Book not properly removed from system! │
│ └──────────┘ │
│ │
│ ✅ CORRECT WAY (Using the return desk): │
│ ┌──────────┐ │
│ │ You have │ │
│ │ book #42│ │
│ │ │ │
│ │ 📞 "I │ │
│ │ want to │ │
│ │ return │ │
│ │ book │ │
│ │ #42!" │ │
│ │ │ │
│ │ (arrow │ │
│ │ function│ │
│ │ with ID)│ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 👩💼 LIBRARIAN (Parent) │ │
│ │ "Book #42? Let me remove it │ │
│ │ from the catalog..." │ │
│ │ │ │
│ │ books.filter(b => b.id !== 42) │ │
│ │ "Done! Book removed!" │ │
│ └─────────────────────────────────┘ │
│ │
│ You told them EXACTLY which book! │
│ They removed it from the system! │
│ Catalog is updated! │
└─────────────────────────────────────────────────┘
The "Security Guard" Analogy
┌─────────────────────────────────────────────────┐
│ THE EVENT OBJECT = A STRANGE PACKAGE │
│ │
│ Scenario: You need to send a package to HQ. │
│ │
│ ❌ WRONG WAY (Sending the mailman instead): │
│ ┌──────────┐ │
│ │ You want │ │
│ │ to send │ │
│ │ Package │ │
│ │ #5 to HQ│ │
│ │ │ │
│ │ 📦 Give │ │
│ │ mailman │ │
│ │ to HQ │ │
│ │ │ │
│ │ (onClick=│ │
│ │ {send} │ │
│ │ without │ │
│ │ package) │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 🏢 HQ receives... THE MAILMAN! │ │
│ │ "Who are you? Where's package │ │
│ │ #5?" │ │
│ │ "I'm just the delivery guy!" │ │
│ │ ❌ CONFUSED! Nothing delivered! │ │
│ └─────────────────────────────────┘ │
│ │
│ ✅ CORRECT WAY (Putting package in truck): │
│ ┌──────────┐ │
│ │ You want │ │
│ │ to send │ │
│ │ Package │ │
│ │ #5 to HQ│ │
│ │ │ │
│ │ 📦 Put │ │
│ │ package │ │
│ │ #5 in │ │
│ │ truck │ │
│ │ │ │
│ │ (onClick=│ │
│ │ {() => │ │
│ │ send(5)} │ │
│ │ with ID) │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 🏢 HQ receives Package #5! │ │
│ │ "Ah! Package #5! Processed!" │ │
│ │ ✅ SUCCESS! │ │
│ └─────────────────────────────────┘ │
│ │
│ The mailman (event) is NOT the package! │
│ You must put the package (ID) in the truck! │
└─────────────────────────────────────────────────┘
The "Filter Sieve" Analogy
┌─────────────────────────────────────────────────┐
│ filter() = A SIEVE THAT KEEPS THE GOOD STUFF │
│ │
│ You have a bucket of rocks: │
│ ┌─────────────────────────────────────────┐ │
│ │ 🪨 🪨 🪨 🪨 🪨 🪨 🪨 🪨 🪨 🪨 │ │
│ │ Rocks: [1, 2, 3, 4, 5] │ │
│ │ You want to REMOVE rock #3! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ❌ WRONG: Pick out rock #3 by hand (mutate!) │
│ ┌─────────────────────────────────────────┐ │
│ │ You reach in and grab rock #3 │ │
│ │ ❌ But you touched the original bucket! │ │
│ │ ❌ React gets angry! You mutated state! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ CORRECT: Use a sieve (filter!) │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ 🪨 🪨 🪨 🪨 🪨 🪨 🪨 🪨 🪨 │ │
│ │ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ 🔲 SIEVE (filter) │ │ │
│ │ │ "Keep rocks where │ │ │
│ │ │ id !== 3" │ │ │
│ │ │ │ │ │
│ │ │ Rock 1: 1 !== 3 → TRUE │ │ │
│ │ │ Rock 2: 2 !== 3 → TRUE │ │ │
│ │ │ Rock 3: 3 !== 3 → FALSE │ │ │
│ │ │ Rock 4: 4 !== 3 → TRUE │ │ │
│ │ │ Rock 5: 5 !== 3 → TRUE │ │ │
│ │ └─────────────────────────┘ │ │
│ │ ↓ ↓ ↓ ↓ ↓ │ │
│ │ 🪨 🪨 🪨 🪨 🪨 │ │
│ │ [1, 2, 4, 5] │ │
│ │ │ │
│ │ Rock 3 fell through! It's gone! │ │
│ │ But the original bucket is untouched! │ │
│ │ React is happy! You created a NEW │ │
│ │ array! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Memory Trick: │
│ "filter keeps what is TRUE, drops what is │
│ FALSE. Like a sieve keeping the big rocks!" │
└─────────────────────────────────────────────────┘
The "Telephone Game" Analogy for Prop Drilling
┌─────────────────────────────────────────────────┐
│ PROP DRILLING = THE TELEPHONE GAME │
│ │
│ App wants to tell Item about the delete │
│ function, but Item is 2 levels down! │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 👨👩👧 APP (Grandparent) │ │
│ │ "Tell Item they can call me to delete!" │ │
│ │ onDeleteItem={handleDeleteItem} │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 📋 PACKING LIST (Parent) │ │
│ │ "I don't need this, but my child does!" │ │
│ │ onDeleteItem={onDeleteItem} │ │
│ │ (passes it through without using it) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 📦 ITEM (Child) │ │
│ │ "Finally! I can call App to delete!" │ │
│ │ onClick={() => onDeleteItem(id)} │ │
│ └─────────────────────────────────────────┘ │
│ │
│ PackingList is like a messenger! │
│ It doesn't use the message itself! │
│ It just passes it to the next person! │
│ │
│ This is called "Prop Drilling" — │
│ passing props through components that │
│ don't need them, just to reach children! │
│ │
│ (Later: Context API fixes this!) │
└─────────────────────────────────────────────────┘
🎓 Practice Exercises
Exercise 1: Identify the Bug
Look at this code. Why won't the delete button work?
function App() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Build App" },
]);
function handleDelete(id) {
setTodos(prev => prev.filter(t => t.id !== id));
}
return <TodoList todos={todos} onDelete={handleDelete} />;
}
function TodoList({ todos, onDelete }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={onDelete}>Delete</button>
</li>
))}
</ul>
);
}
Questions:
- What does
onClick={onDelete}pass to the function? → The event object! - What does
handleDeleteexpect? → An ID number! - What will
filtercompare? →t.id !== [object MouseEvent]→ Always true! - How do we fix it? →
onClick={() => onDelete(todo.id)}
Solution:
function TodoList({ todos, onDelete }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
{/* ✅ WRAP IN ARROW FUNCTION! */}
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}
Exercise 2: Build the Delete Flow
Fill in the blanks to make deletion work:
function App() {
const [items, setItems] = useState([
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
]);
function handleDeleteItem(_____) {
setItems(prev => prev.filter(item => item.id !== _____));
}
return (
<List items={items} onDeleteItem={______________} />
);
}
function List({ items, onDeleteItem }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={_____ => onDeleteItem(_____)}>❌</button>
</li>
))}
</ul>
);
}
Solution:
function App() {
const [items, setItems] = useState([
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
]);
function handleDeleteItem(id) { // ← id
setItems(prev => prev.filter(item => item.id !== id)); // ← id
}
return (
<List items={items} onDeleteItem={handleDeleteItem} /> // ← handleDeleteItem
);
}
function List({ items, onDeleteItem }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => onDeleteItem(item.id)}>❌</button> // ← () and item.id
</li>
))}
</ul>
);
}
Exercise 3: The Complete Decision Flowchart
Walk through the flowchart for deleting a todo:
Questions:
- Where does the todo state live? → App
- Where is the delete button? → TodoItem
- How does TodoItem know WHICH todo to delete? → Pass ID in arrow function!
- What array method removes the item immutably? → filter()
- What does filter keep? → Items where condition is TRUE!
- What happens after state updates? → React re-renders!
Solution:
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: "Learn React", done: false },
{ id: 2, text: "Build App", done: false },
]);
function handleDeleteTodo(id) {
// filter keeps items where id !== idToDelete
setTodos(prev => prev.filter(todo => todo.id !== id));
}
function handleToggleTodo(id) {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
}
return (
<div>
<TodoList
todos={todos}
onDeleteTodo={handleDeleteTodo}
onToggleTodo={handleToggleTodo}
/>
</div>
);
}
function TodoList({ todos, onDeleteTodo, onToggleTodo }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDeleteTodo={onDeleteTodo}
onToggleTodo={onToggleTodo}
/>
))}
</ul>
);
}
function TodoItem({ todo, onDeleteTodo, onToggleTodo }) {
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => onDeleteTodo(todo.id)}>Delete</button>
</li>
);
}
💡 Key Takeaways
| Concept | What It Means | Example |
|---|---|---|
| Deleting Items | Removing an item from state array | filter(item => item.id !== id) |
| filter() | Creates new array, excludes matching items | items.filter(i => i.id !== 2) |
| Immutable Delete | Never mutate original array | Always create new array with filter |
| Event Object | What React passes if you don't use arrow function | SyntheticBaseEvent { ... } |
| Arrow Function Wrapper | () => handler(arg) to pass custom arguments | onClick={() => onDelete(id)} |
| Prop Drilling | Passing props through intermediate components | App → List → Item |
| ID as Argument | Tells parent exactly WHICH item to delete | handleDeleteItem(item.id) |
| Child-to-Parent | Child calls parent's function to update | Inverse data flow pattern |
The Delete Item Pattern:
function DeletePattern() {
// Step 1: State in parent
const [items, setItems] = useState([]);
// Step 2: Delete handler in parent
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
// Step 3: Pass handler down
// Step 4: Pass through intermediate components
// Step 5: Wrap in arrow function with ID
// Step 6: Parent filters and updates!
}
Golden Rules:
- Always wrap event handlers in arrow functions when passing arguments —
onClick={() => handler(id)}NOTonClick={handler} - Never mutate state — Use
filter(), neversplice(),pop(), ordelete - filter keeps TRUE, removes FALSE —
item.id !== idkeeps everything except the match - Pass the ID — The child must tell the parent WHICH item to delete
- Prop drill when necessary — Pass functions through intermediate components
- Console.log your arguments — If deletion isn't working, check what you're receiving!
- Naming convention —
handleDeleteItemin parent,onDeleteItemas prop name
One Sentence Summary:
> "Deleting items in React requires lifting the items state to a common parent component, creating a delete handler function that uses the immutable filter method to return a new array excluding the item with the matching ID, passing that handler function down through intermediate components as a prop, and wrapping it in an arrow function at the event handler level to pass the specific item's ID rather than the event object, ensuring React's one-way data flow is maintained while allowing child components to communicate exactly which item should be removed from the parent's state!"