▶️ Live demo
Try it yourself — interact with the example below.
Loading demo…
🎯 What Is an Accordion?
Imagine a filing cabinet with folders:
WITHOUT ACCORDION (Everything Visible):
┌─────────────────────────────────────────┐
│ 📁 ALL PAPERS STAPLED TOGETHER │
│ │
│ Question 1: What is React? │
│ Answer: React is a JavaScript library...│
│ (Can't hide! Takes up space!) │
│ │
│ Question 2: Why use React? │
│ Answer: Because it's fast and efficient...│
│ (Can't hide! Takes up space!) │
│ │
│ Question 3: How do I start? │
│ Answer: You need Node.js and... │
│ (Can't hide! Takes up space!) │
│ │
│ ❌ All answers visible at once! │
│ ❌ Takes up too much space! │
│ ❌ User can't focus on one question! │
└─────────────────────────────────────────┘
WITH ACCORDION (Click to Expand):
┌─────────────────────────────────────────┐
│ 📁 SMART FILING CABINET │
│ │
│ 📂 01: What is React? [+] │
│ (Closed - click to open) │
│ ─────────────────────────────────────── │
│ 📂 02: Why use React? [+] │
│ (Closed - click to open) │
│ ─────────────────────────────────────── │
│ 📂 03: How do I start? [+] │
│ (Closed - click to open) │
│ │
│ Click one: │
│ 📂 01: What is React? [-] │
│ │ React is a JavaScript library... │
│ │ for building user interfaces... │
│ │ (Content visible! Space efficient!) │
│ ─────────────────────────────────────── │
│ 📂 02: Why use React? [+] │
│ 📂 03: How do I start? [+] │
│ │
│ ✅ Only open what you need! │
│ ✅ Saves space! │
│ ✅ User focuses on one thing! │
│ ✅ Each folder works independently! │
└─────────────────────────────────────────┘
⚠️ The Big Problem: "Where Should State Live?"
// ==========================================
// THE "STATE IN WRONG PLACE" TRAP
// ==========================================
// ❌ WRONG: State in parent, controls ALL items
function BadAccordion() {
const [isOpen, setIsOpen] = useState(false); // ← One state for ALL!
return (
<div>
{/* All items share ONE state! */}
<AccordionItem isOpen={isOpen} title="Q1" />
<AccordionItem isOpen={isOpen} title="Q2" />
<AccordionItem isOpen={isOpen} title="Q3" />
{/* Click one → ALL open or ALL close! */}
</div>
);
}
// Problems:
// 1. One state controls ALL items
// 2. Opening one opens ALL of them!
// 3. Can't have multiple open at once!
// 4. Not what user expects!
// ==========================================
// THE SOLUTION: Each Item Has Its Own State
// ==========================================
// ✅ CORRECT: Each item has INDEPENDENT state
function GoodAccordion() {
return (
<div>
{/* Each item has its OWN state! */}
<AccordionItem title="Q1" /> {/* Has isOpen: false */}
<AccordionItem title="Q2" /> {/* Has isOpen: false */}
<AccordionItem title="Q3" /> {/* Has isOpen: false */}
{/* Click one → ONLY that one opens! */}
</div>
);
}
function AccordionItem({ title }) {
const [isOpen, setIsOpen] = useState(false); // ← Each item's OWN state!
return (
<div onClick={() => setIsOpen(!isOpen)}>
<h3>{title} {isOpen ? '-' : '+'}</h3>
{isOpen && <p>Answer content here...</p>}
</div>
);
}
// What happens now:
// 1. Each item has its OWN "memory" (isOpen)
// 2. Click item 1 → item 1 opens, others stay closed!
// 3. Click item 2 → item 2 opens, item 1 stays open!
// 4. Each operates INDEPENDENTLY!
📋 Complete Visual Examples
Create file: react-accordion.js
// ==========================================
// ACCORDION IN REACT - Complete Guide
// ==========================================
/*
ACCORDION ANATOMY:
┌─────────────────────────────────────────┐
│ <Accordion data={faqs}> │
│ ← Parent container, receives data │
│ │
│ <AccordionItem num={0} title="..." │
│ text="..." /> │
│ ← Each item has: number, title, text │
│ │
│ Inside AccordionItem: │
│ • Number (01, 02, 03) │
│ • Title (the question) │
│ • Icon (+ or -) │
│ • Content (answer, hidden or shown) │
│ │
│ State: Each item has its OWN isOpen! │
└─────────────────────────────────────────┘
THE MAP INDEX TRICK:
┌─────────────────────────────────────────┐
│ data.map((element, index) => ...) │
│ │
│ element = { title: "...", text: "..." } │
│ index = 0, 1, 2 (position in array) │
│ │
│ Number formatting: │
│ index 0 → "01" (0 < 9, so add "0") │
│ index 1 → "02" (1 < 9, so add "0") │
│ index 2 → "03" (2 < 9, so add "0") │
│ index 9 → "10" (9 not < 9, just 10) │
│ │
│ {num < 9 ? `0${num + 1}` : num + 1} │
└─────────────────────────────────────────┘
THE 3-STEP STATE PATTERN:
┌─────────────────────────────────────────┐
│ Step 1: DEFINE the state │
│ const [isOpen, setIsOpen] = useState(false)│
│ │
│ Step 2: USE the state in JSX │
│ {isOpen && <div>Content</div>} │
│ {isOpen ? '-' : '+'} │
│ │
│ Step 3: UPDATE the state on events │
│ onClick={() => setIsOpen(!isOpen)} │
│ │
│ Think: "What changes?" → "State!" │
│ "When does it change?" → "Click!" │
└─────────────────────────────────────────┘
*/
// ==========================================
// EXAMPLE 1: The Data Structure
// ==========================================
// The FAQ data - array of objects
const faqs = [
{
title: "Where are these chairs assembled?",
text: "Lorem ipsum dolor sit amet consectetur, adipisicing elit..."
},
{
title: "How long do I have to return my chair?",
text: "Accusantium voluptatum..."
},
{
title: "Do you ship to countries outside the EU?",
text: "Excepturi velit..."
}
];
// Visual:
// faqs = [
// { title: "Question 1", text: "Answer 1" }, ← index 0
// { title: "Question 2", text: "Answer 2" }, ← index 1
// { title: "Question 3", text: "Answer 3" } ← index 2
// ]
// ==========================================
// EXAMPLE 2: The Number Formatting Trick
// ==========================================
// MANUAL WAY (Hard-coded):
// <p>01</p>
// <p>02</p>
// <p>03</p>
// SMART WAY (Dynamic, based on index):
// num = 0 → 0 < 9 ? "0" + (0+1) : 0+1 → "01"
// num = 1 → 1 < 9 ? "0" + (1+1) : 1+1 → "02"
// num = 2 → 2 < 9 ? "0" + (2+1) : 2+1 → "03"
// num = 9 → 9 < 9 ? false → 9+1 → "10"
// Code:
// {num < 9 ? `0${num + 1}` : num + 1}
// ==========================================
// EXAMPLE 3: Accordion Component (Parent)
// ==========================================
function Accordion({ data }) {
// data = the faqs array passed in as a prop
return (
<div className="accordion">
{data.map((element, index) => (
<AccordionItem
key={element.title} // ← Unique identifier for React
num={index} // ← 0, 1, 2 (for numbering)
title={element.title} // ← "Where are these chairs..."
text={element.text} // ← The answer text
/>
))}
</div>
);
}
// How it's used:
// <Accordion data={faqs} />
// Visual Flow:
// App
// │
// └── Accordion (receives data={faqs})
// │
// ├── AccordionItem (num=0, title="Where...", text="Lorem...")
// ├── AccordionItem (num=1, title="How long...", text="Accusantium...")
// └── AccordionItem (num=2, title="Do you...", text="Excepturi...")
// ==========================================
// EXAMPLE 4: AccordionItem Component (Child)
// ==========================================
function AccordionItem({ num, title, text }) {
// Props received:
// num → 0, 1, 2 (array index)
// title → "Where are these chairs assembled?"
// text → "Lorem ipsum dolor sit amet..."
return (
<div className="item">
{/* Number: 01, 02, 03 */}
<p className="number">{num < 9 ? `0${num + 1}` : num + 1}</p>
{/* Title: The question */}
<p className="title">{title}</p>
{/* Icon: + or - */}
<p className="icon">-</p>
{/* Content: The answer (hidden or shown) */}
<div className="content-box">{text}</div>
</div>
);
}
// ==========================================
// EXAMPLE 5: Adding State (The Complete Version)
// ==========================================
import { useState } from 'react';
function AccordionItem({ num, title, text }) {
// ==========================================
// STEP 1: DEFINE STATE
// ==========================================
// isOpen = false means "closed" by default
// setIsOpen = function to change it
const [isOpen, setIsOpen] = useState(false);
// ==========================================
// STEP 3: EVENT HANDLER (for updating)
// ==========================================
function handleToggle() {
setIsOpen((current) => !current);
// !current means: flip between true and false
// true → false (close)
// false → true (open)
}
return (
<div
className={`item ${isOpen ? "open" : ""}`} // Add "open" class when open
onClick={handleToggle} // Click anywhere to toggle
>
{/* Number */}
<p className="number">{num < 9 ? `0${num + 1}` : num + 1}</p>
{/* Title */}
<p className="title">{title}</p>
{/* Icon: - if open, + if closed */}
<p className="icon">{isOpen ? "-" : "+"}</p>
{/* ==========================================
STEP 2: USE STATE (conditional rendering)
========================================== */}
{isOpen && (
<div className="content-box">{text}</div>
)}
{/*
isOpen = false → {false && ...} → NOTHING shown
isOpen = true → {true && ...} → Content shown!
*/}
</div>
);
}
// ==========================================
// EXAMPLE 6: Complete App
// ==========================================
const faqs = [
{
title: "Where are these chairs assembled?",
text: "Lorem ipsum dolor sit amet consectetur, adipisicing elit..."
},
{
title: "How long do I have to return my chair?",
text: "Accusantium voluptatum..."
},
{
title: "Do you ship to countries outside the EU?",
text: "Excepturi velit..."
}
];
function App() {
return (
<div>
<Accordion data={faqs} />
</div>
);
}
export default App;
🚀 Interactive React Usage Examples
Complete React File: AccordionMasterClass.jsx
import React, { useState } from 'react';
// ==========================================
// INTERACTIVE ACCORDION DEMO
// ==========================================
function AccordionMasterClass() {
const [activeDemo, setActiveDemo] = useState('anatomy');
const demos = {
anatomy: { title: 'Accordion Anatomy', component: <AccordionAnatomyDemo /> },
static: { title: 'Static Version', component: <StaticVersionDemo /> },
state: { title: 'Adding State', component: <AddingStateDemo /> },
complete: { title: 'Complete Accordion', component: <CompleteAccordionDemo /> },
independent: { title: 'Independent State', component: <IndependentStateDemo /> }
};
return (
<div className="container py-5">
<header className="bg-primary text-white p-4 rounded-3 mb-4">
<h1 className="m-0">📂 Accordion in React</h1>
<p className="mb-0 mt-2 opacity-75">Building Expandable/Collapsible Content Panels</p>
</header>
<nav className="d-flex gap-2 mb-4 flex-wrap">
{Object.entries(demos).map(([key, { title }]) => (
<button
key={key}
onClick={() => setActiveDemo(key)}
className={`btn ${activeDemo === key ? 'btn-primary' : 'btn-outline-secondary'}`}
>
{title}
</button>
))}
</nav>
<main className="bg-light p-4 rounded-3" style={{ minHeight: '400px' }}>
{demos[activeDemo].component}
</main>
</div>
);
}
// ==========================================
// DEMO 1: Accordion Anatomy
// ==========================================
function AccordionAnatomyDemo() {
const [highlighted, setHighlighted] = useState(null);
const parts = [
{ id: 'accordion', name: '<Accordion>', desc: 'Parent container, receives data prop', color: 'primary' },
{ id: 'item', name: '<AccordionItem>', desc: 'Individual panel with number, title, text', color: 'success' },
{ id: 'number', name: 'Number', desc: 'Formatted index (01, 02, 03)', color: 'warning' },
{ id: 'title', name: 'Title', desc: 'The question or heading', color: 'info' },
{ id: 'icon', name: 'Icon', desc: '+ when closed, - when open', color: 'danger' },
{ id: 'content', name: 'Content', desc: 'The answer text (hidden/shown)', color: 'dark' }
];
return (
<div>
<h2 className="mb-4">Accordion Anatomy 🏗️</h2>
<div className="alert alert-info">
<h5 className="alert-heading">An Accordion has 6 parts:</h5>
</div>
<div className="card shadow-sm">
<div className="card-body">
{/* Visual representation of an accordion item */}
<div
className={`p-3 rounded-3 border ${highlighted ? 'border-3' : 'border'}`}
style={{ borderColor: highlighted ? `var(--bs-${parts.find(p => p.id === highlighted)?.color})` : undefined }}
>
<div className="d-flex align-items-center gap-3 mb-2">
<span
onClick={() => setHighlighted('number')}
className={`badge fs-5 ${highlighted === 'number' ? 'bg-warning text-dark' : 'bg-secondary'}`}
style={{ cursor: 'pointer' }}
>
01
</span>
<span
onClick={() => setHighlighted('title')}
className={`flex-grow-1 fw-bold ${highlighted === 'title' ? 'text-info' : ''}`}
style={{ cursor: 'pointer' }}
>
Where are these chairs assembled?
</span>
<span
onClick={() => setHighlighted('icon')}
className={`badge fs-5 ${highlighted === 'icon' ? 'bg-danger' : 'bg-secondary'}`}
style={{ cursor: 'pointer' }}
>
+
</span>
</div>
<div
onClick={() => setHighlighted('content')}
className={`p-3 rounded-2 ${highlighted === 'content' ? 'bg-dark text-white' : 'bg-light'}`}
style={{ cursor: 'pointer' }}
>
<p className="mb-0">Lorem ipsum dolor sit amet consectetur, adipisicing elit...</p>
</div>
</div>
<div className="mt-3 text-center">
<span
onClick={() => setHighlighted('item')}
className={`badge ${highlighted === 'item' ? 'bg-success' : 'bg-secondary'}`}
style={{ cursor: 'pointer' }}
>
<AccordionItem>
</span>
</div>
<div className="mt-2 text-center">
<span
onClick={() => setHighlighted('accordion')}
className={`badge ${highlighted === 'accordion' ? 'bg-primary' : 'bg-secondary'}`}
style={{ cursor: 'pointer' }}
>
{`<Accordion data={faqs}>`}
</span>
</div>
</div>
</div>
{highlighted && (
<div className="alert alert-warning mt-4">
<h5 className="alert-heading" style={{ color: `var(--bs-${parts.find(p => p.id === highlighted)?.color})` }}>
{parts.find(p => p.id === highlighted)?.name}
</h5>
<p className="mb-0">{parts.find(p => p.id === highlighted)?.desc}</p>
</div>
)}
</div>
);
}
// ==========================================
// DEMO 2: Static Version (No State)
// ==========================================
function StaticVersionDemo() {
const faqs = [
{ title: "What is React?", text: "React is a JavaScript library for building user interfaces." },
{ title: "Why use React?", text: "Because it's fast, efficient, and component-based." },
{ title: "How do I start?", text: "You need Node.js, npm, and create-react-app." }
];
return (
<div>
<h2 className="mb-4">Static Version (No State) 📄</h2>
<div className="alert alert-info">
<h5 className="alert-heading">First, build the static version. No state yet!</h5>
</div>
<div className="card">
<div className="card-header bg-success text-white">
<h5 className="mb-0">Static AccordionItem Component:</h5>
</div>
<div className="card-body">
<pre className="bg-dark text-light p-3 rounded mb-0">
{`function AccordionItem({ num, title, text }) {
return (
<div className="item">
<p className="number">
{num < 9 ? \`0\${num + 1}\` : num + 1}
</p>
<p className="title">{title}</p>
<p className="icon">-</p>
<div className="content-box">{text}</div>
</div>
);
}`}
</pre>
</div>
</div>
<div className="mt-4">
<h5>Result:</h5>
{faqs.map((faq, index) => (
<div key={faq.title} className="card mb-2">
<div className="card-body d-flex align-items-center gap-3">
<span className="badge bg-secondary fs-6">
{index < 9 ? `0${index + 1}` : index + 1}
</span>
<span className="flex-grow-1 fw-bold">{faq.title}</span>
<span className="badge bg-secondary">-</span>
</div>
<div className="card-footer">
<p className="mb-0 text-muted">{faq.text}</p>
</div>
</div>
))}
</div>
<div className="alert alert-warning mt-4">
<h5 className="alert-heading">🧠 Problem:</h5>
<p className="mb-0">Everything is visible! We can't click to expand/collapse. We need STATE!</p>
</div>
</div>
);
}
// ==========================================
// DEMO 3: Adding State (The 3-Step Process)
// ==========================================
function AddingStateDemo() {
const [step, setStep] = useState(1);
const steps = [
{
num: 1,
title: 'Define State',
code: 'const [isOpen, setIsOpen] = useState(false);',
visual: '📦',
desc: 'Create a "memory" for each item. false = closed by default.'
},
{
num: 2,
title: 'Use State in JSX',
code: '{isOpen && <div className="content-box">{text}</div>}',
visual: '👁️',
desc: 'Show content ONLY when isOpen is true. Hide when false.'
},
{
num: 3,
title: 'Update State on Click',
code: 'onClick={() => setIsOpen(!isOpen)}',
visual: '🖱️',
desc: 'Flip the state when user clicks. true → false, false → true.'
}
];
const current = steps[step - 1];
return (
<div>
<h2 className="mb-4">Adding State (3-Step Process) ⚡</h2>
<div className="alert alert-info">
<h5 className="alert-heading">The same pattern we always use!</h5>
</div>
<div className="d-flex gap-2 justify-content-center mb-4">
{steps.map(s => (
<button
key={s.num}
onClick={() => setStep(s.num)}
className={`btn btn-lg rounded-circle ${step >= s.num ? 'btn-primary' : 'btn-outline-secondary'}`}
style={{ width: '60px', height: '60px' }}
>
{s.num}
</button>
))}
</div>
<div className="card border-primary">
<div className="card-body">
<h3 className="text-primary">Step {current.num}: {current.title}</h3>
<p className="fs-5 fw-bold text-secondary">{current.desc}</p>
<div className="row g-4">
<div className="col-md-8">
<pre className="bg-dark text-light p-3 rounded">
{current.code}
</pre>
</div>
<div className="col-md-4 d-flex align-items-center justify-content-center">
<div className="display-1">{current.visual}</div>
</div>
</div>
</div>
</div>
<div className="alert alert-success mt-4">
<h5 className="alert-heading">🧠 Memory Trick:</h5>
<p className="mb-0">"Define it, Use it, Update it" — The 3-step state dance!</p>
</div>
</div>
);
}
// ==========================================
// DEMO 4: Complete Accordion
// ==========================================
function CompleteAccordionDemo() {
const faqs = [
{ title: "What is React?", text: "React is a JavaScript library for building user interfaces." },
{ title: "Why use React?", text: "Because it's fast, efficient, and component-based." },
{ title: "How do I start?", text: "You need Node.js, npm, and create-react-app." }
];
return (
<div>
<h2 className="mb-4">Complete Accordion 🎉</h2>
<div className="alert alert-info">
<h5 className="alert-heading">Everything put together!</h5>
</div>
<div className="card shadow-lg">
<div className="card-header bg-primary text-white">
<h5 className="mb-0">FAQ Accordion</h5>
</div>
<div className="card-body">
<Accordion data={faqs} />
</div>
</div>
<div className="alert alert-success mt-4">
<h5 className="alert-heading">✅ How It Works:</h5>
<ol className="mb-0">
<li>Accordion receives data prop (array of FAQs)</li>
<li>Maps over data to create AccordionItem components</li>
<li>Each AccordionItem gets num, title, text props</li>
<li>Each AccordionItem has its OWN isOpen state</li>
<li>Click an item → its state flips → it opens/closes</li>
<li>Other items stay the same!</li>
</ol>
</div>
</div>
);
}
function Accordion({ data }) {
return (
<div className="accordion">
{data.map((element, index) => (
<AccordionItem
key={element.title}
num={index}
title={element.title}
text={element.text}
/>
))}
</div>
);
}
function AccordionItem({ num, title, text }) {
const [isOpen, setIsOpen] = useState(false);
function handleToggle() {
setIsOpen((current) => !current);
}
return (
<div
className={`card mb-2 ${isOpen ? 'border-success border-2' : ''}`}
onClick={handleToggle}
style={{ cursor: 'pointer' }}
>
<div className={`card-body d-flex align-items-center gap-3 ${isOpen ? 'bg-success-subtle' : ''}`}>
<span className={`badge fs-6 ${isOpen ? 'bg-success' : 'bg-secondary'}`}>
{num < 9 ? `0${num + 1}` : num + 1}
</span>
<span className={`flex-grow-1 fw-bold ${isOpen ? 'text-success' : ''}`}>
{title}
</span>
<span className={`badge fs-5 ${isOpen ? 'bg-success' : 'bg-secondary'}`}>
{isOpen ? '-' : '+'}
</span>
</div>
{isOpen && (
<div className="card-footer bg-light">
<p className="mb-0">{text}</p>
</div>
)}
</div>
);
}
// ==========================================
// DEMO 5: Independent State (Why It Matters)
// ==========================================
function IndependentStateDemo() {
const [sharedState, setSharedState] = useState(false);
return (
<div>
<h2 className="mb-4">Independent State vs Shared State 🆚</h2>
<div className="alert alert-info">
<h5 className="alert-heading">This is the KEY insight!</h5>
</div>
<div className="row g-4">
{/* SHARED STATE (WRONG) */}
<div className="col-md-6">
<div className="card border-danger">
<div className="card-header bg-danger text-white">
<h5 className="mb-0">❌ Shared State (Wrong)</h5>
</div>
<div className="card-body">
<p className="text-muted small">One state controls ALL items:</p>
<pre className="bg-dark text-light p-2 rounded small">
{`const [isOpen, setIsOpen] = useState(false);
// All items share ONE state!
<Item isOpen={isOpen} />
<Item isOpen={isOpen} />
<Item isOpen={isOpen} />`}
</pre>
<div className="mt-3">
<button
className="btn btn-danger btn-sm mb-2"
onClick={() => setSharedState(!sharedState)}
>
Toggle All
</button>
<div className="d-flex flex-column gap-2">
<div className={`p-2 rounded ${sharedState ? 'bg-success text-white' : 'bg-light'}`}>
Item 1 {sharedState ? '(OPEN)' : '(CLOSED)'}
</div>
<div className={`p-2 rounded ${sharedState ? 'bg-success text-white' : 'bg-light'}`}>
Item 2 {sharedState ? '(OPEN)' : '(CLOSED)'}
</div>
<div className={`p-2 rounded ${sharedState ? 'bg-success text-white' : 'bg-light'}`}>
Item 3 {sharedState ? '(OPEN)' : '(CLOSED)'}
</div>
</div>
</div>
<p className="text-danger small mt-2 mb-0">
Click → ALL open or ALL close together!
</p>
</div>
</div>
</div>
{/* INDEPENDENT STATE (CORRECT) */}
<div className="col-md-6">
<div className="card border-success">
<div className="card-header bg-success text-white">
<h5 className="mb-0">✅ Independent State (Correct)</h5>
</div>
<div className="card-body">
<p className="text-muted small">Each item has its OWN state:</p>
<pre className="bg-dark text-light p-2 rounded small">
{`function Item() {
const [isOpen, setIsOpen] = useState(false);
// Each item has its OWN state!
return <div onClick={toggle}>...</div>;
}`}
</pre>
<div className="mt-3">
<IndependentItem label="Item 1" />
<IndependentItem label="Item 2" />
<IndependentItem label="Item 3" />
</div>
<p className="text-success small mt-2 mb-0">
Click each one independently!
</p>
</div>
</div>
</div>
</div>
<div className="alert alert-warning mt-4">
<h5 className="alert-heading">🧠 The Rule:</h5>
<p className="mb-0">
If items operate <strong>independently</strong> → Each gets its <strong>own state</strong>!<br/>
If items must stay <strong>in sync</strong> → State goes in <strong>parent</strong>!
</p>
</div>
</div>
);
}
function IndependentItem({ label }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={`p-2 rounded mb-2 ${isOpen ? 'bg-success text-white' : 'bg-light'}`}
onClick={() => setIsOpen(!isOpen)}
style={{ cursor: 'pointer' }}
>
{label} {isOpen ? '(OPEN)' : '(CLOSED)'}
</div>
);
}
export default AccordionMasterClass;
🧠 Memory Aids for Poor Logic Thinking
The "Filing Cabinet" Analogy
┌─────────────────────────────────────────────────┐
│ ACCORDION = FILING CABINET WITH FOLDERS │
│ │
│ WITHOUT ACCORDION: │
│ ┌─────────────────────────────────────────┐ │
│ │ All papers spread on desk │ │
│ │ Can't find anything! │ │
│ │ Takes up all space! │ │
│ │ Overwhelming! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ WITH ACCORDION: │
│ ┌─────────────────────────────────────────┐ │
│ │ 📁 Folder 1: "What is React?" │ │
│ │ [Click] → Opens → Shows answer │ │
│ │ [Click] → Closes → Hides answer │ │
│ │ │ │
│ │ 📁 Folder 2: "Why React?" │ │
│ │ [Click] → Opens → Shows answer │ │
│ │ (Folder 1 stays closed!) │ │
│ │ │ │
│ │ Each folder has its own lock! │ │
│ │ (Each has its own state!) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ State = "Is this folder open or closed?" │
│ Each folder remembers its own state! │
└─────────────────────────────────────────────────┘
The "Light Switch" Analogy for State
┌─────────────────────────────────────────────────┐
│ STATE = LIGHT SWITCH IN EACH ROOM │
│ │
│ Your house has 3 rooms: │
│ ┌─────────────────────────────────────────┐ │
│ │ 🏠 THE HOUSE (Your App) │ │
│ │ │ │
│ │ Room 1: [Light Switch] → OFF │ │
│ │ Room 2: [Light Switch] → OFF │ │
│ │ Room 3: [Light Switch] → OFF │ │
│ │ │ │
│ │ Each room has its OWN switch! │ │
│ │ (Each AccordionItem has its OWN state!)│ │
│ └─────────────────────────────────────────┘ │
│ │
│ You flip switch in Room 1: │
│ Room 1: [Light Switch] → ON ← CHANGED! │
│ Room 2: [Light Switch] → OFF (unchanged) │
│ Room 3: [Light Switch] → OFF (unchanged) │
│ │
│ Only Room 1's light turns on! │
│ Other rooms stay dark! │
│ │
│ This is INDEPENDENT STATE! │
│ Each room (AccordionItem) has its OWN switch!│
└─────────────────────────────────────────────────┘
The "Number Formatting" Analogy
┌─────────────────────────────────────────────────┐
│ NUMBER FORMATTING = ADDING LEADING ZERO │
│ │
│ You have boxes numbered 1, 2, 3... │
│ But you want them to look neat: 01, 02, 03... │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Box 0: Is 0 less than 9? YES! │ │
│ │ Add "0" in front: "0" + 1 = "01"│ │
│ │ │ │
│ │ Box 1: Is 1 less than 9? YES! │ │
│ │ Add "0" in front: "0" + 2 = "02"│ │
│ │ │ │
│ │ Box 8: Is 8 less than 9? YES! │ │
│ │ Add "0" in front: "0" + 9 = "09"│ │
│ │ │ │
│ │ Box 9: Is 9 less than 9? NO! │ │
│ │ Just show: 9 + 1 = "10" │ │
│ │ │ │
│ │ Box 10: Is 10 less than 9? NO! │ │
│ │ Just show: 10 + 1 = "11" │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Code: {num < 9 ? `0${num + 1}` : num + 1} │
│ Translation: "If num is less than 9, add a 0 │
│ in front. Otherwise, just show the number." │
└─────────────────────────────────────────────────┘
The "Conditional Rendering" Analogy
┌─────────────────────────────────────────────────┐
│ CONDITIONAL RENDERING = MAGIC CURTAIN │
│ │
│ {isOpen && <div>Content</div>} │
│ │
│ Think of && as a security guard: │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ isOpen = false │ │
│ │ {false && <div>Content</div>} │ │
│ │ │ │
│ │ Security Guard: "Is it true?" │ │
│ │ false → "NO! Content stays hidden!" │ │
│ │ Result: NOTHING shown │ │
│ │ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ [CURTAIN CLOSED] │ │ │
│ │ │ Content hidden behind! │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ isOpen = true │ │
│ │ {true && <div>Content</div>} │ │
│ │ │ │
│ │ Security Guard: "Is it true?" │ │
│ │ true → "YES! Show the content!" │ │
│ │ Result: Content appears! │ │
│ │ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ [CURTAIN OPEN] │ │ │
│ │ │ Content is visible! │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ MEMORY: && = "AND" = "Both must be true!" │
│ {condition && content} = "If condition, show!"│
└─────────────────────────────────────────────────┘
🎓 Practice Exercises
Exercise 1: Create Numbered Items
Create a component that shows numbered items from an array:
function NumberedList() {
const items = ["Apple", "Banana", "Cherry"];
// TODO: Map over items and show 01, 02, 03 with each name
return (
<div>
{/* Your code here */}
</div>
);
}
Solution:
function NumberedList() {
const items = ["Apple", "Banana", "Cherry"];
return (
<div>
{items.map((item, index) => (
<div key={item} className="d-flex gap-2 mb-2">
<span className="badge bg-secondary">
{index < 9 ? `0${index + 1}` : index + 1}
</span>
<span>{item}</span>
</div>
))}
</div>
);
}
Exercise 2: Toggle Visibility
Create a component that shows/hides text on click:
function ToggleText() {
// TODO: Add state for visibility
// TODO: Show "Hello!" when visible, hide when not
// TODO: Click to toggle
return (
<div>
{/* Your code here */}
</div>
);
}
Solution:
import { useState } from 'react';
function ToggleText() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button
className="btn btn-primary"
onClick={() => setIsVisible(!isVisible)}
>
{isVisible ? 'Hide' : 'Show'} Text
</button>
{isVisible && (
<div className="alert alert-info mt-3">
Hello! I'm visible!
</div>
)}
</div>
);
}
Exercise 3: Complete Accordion Item
Build a single accordion item with state:
function SingleAccordionItem({ num, title, text }) {
// TODO: Add isOpen state
// TODO: Show + when closed, - when open
// TODO: Show text only when open
// TODO: Click to toggle
return (
<div className="card">
{/* Your code here */}
</div>
);
}
Solution:
import { useState } from 'react';
function SingleAccordionItem({ num, title, text }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={`card mb-2 ${isOpen ? 'border-success' : ''}`}
onClick={() => setIsOpen(!isOpen)}
style={{ cursor: 'pointer' }}
>
<div className="card-body d-flex align-items-center gap-3">
<span className="badge bg-secondary">
{num < 9 ? `0${num + 1}` : num + 1}
</span>
<span className="flex-grow-1 fw-bold">{title}</span>
<span className="badge bg-secondary">
{isOpen ? '-' : '+'}
</span>
</div>
{isOpen && (
<div className="card-footer">
<p className="mb-0">{text}</p>
</div>
)}
</div>
);
}
Exercise 4: Complete Accordion
Build the complete accordion with multiple items:
function Accordion() {
const faqs = [
{ title: "What is HTML?", text: "HTML is the structure of web pages." },
{ title: "What is CSS?", text: "CSS is the styling of web pages." },
{ title: "What is JavaScript?", text: "JavaScript adds interactivity." }
];
// TODO: Create AccordionItem component
// TODO: Map over faqs to create items
// TODO: Each item should have independent state
return (
<div>
{/* Your code here */}
</div>
);
}
Solution:
import { useState } from 'react';
function Accordion() {
const faqs = [
{ title: "What is HTML?", text: "HTML is the structure of web pages." },
{ title: "What is CSS?", text: "CSS is the styling of web pages." },
{ title: "What is JavaScript?", text: "JavaScript adds interactivity." }
];
return (
<div className="accordion">
{faqs.map((faq, index) => (
<AccordionItem
key={faq.title}
num={index}
title={faq.title}
text={faq.text}
/>
))}
</div>
);
}
function AccordionItem({ num, title, text }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={`card mb-2 ${isOpen ? 'border-primary' : ''}`}
onClick={() => setIsOpen(!isOpen)}
style={{ cursor: 'pointer' }}
>
<div className={`card-body d-flex align-items-center gap-3 ${isOpen ? 'bg-primary-subtle' : ''}`}>
<span className={`badge ${isOpen ? 'bg-primary' : 'bg-secondary'}`}>
{num < 9 ? `0${num + 1}` : num + 1}
</span>
<span className="flex-grow-1 fw-bold">{title}</span>
<span className={`badge ${isOpen ? 'bg-primary' : 'bg-secondary'}`}>
{isOpen ? '-' : '+'}
</span>
</div>
{isOpen && (
<div className="card-footer">
<p className="mb-0">{text}</p>
</div>
)}
</div>
);
}
💡 Key Takeaways
| Concept | What It Means | Example |
|---|---|---|
| Accordion | UI pattern where content expands/collapses | FAQ sections, settings panels |
| Independent State | Each item has its OWN state | Each AccordionItem has its own isOpen |
| map() with index | Loop over array, get position | data.map((item, index) => ...) |
| Number formatting | Add leading zero for single digits | {num < 9 ? '0' + (num+1) : num+1} |
| Conditional rendering | Show/hide based on state | {isOpen && <div>Content</div>} |
| Conditional classes | Change style based on state | ` item ${isOpen ? 'open' : ''} ` |
| Event handler | Function that runs on user action | onClick={handleToggle} |
| State callback | Safe update using previous value | setIsOpen(current => !current) |
| key prop | Unique identifier for list items | key={element.title} |
The Complete Accordion Pattern:
function Accordion({ data }) {
return (
<div className="accordion">
{data.map((element, index) => (
<AccordionItem
key={element.title}
num={index}
title={element.title}
text={element.text}
/>
))}
</div>
);
}
function AccordionItem({ num, title, text }) {
const [isOpen, setIsOpen] = useState(false);
function handleToggle() {
setIsOpen((current) => !current);
}
return (
<div
className={`item ${isOpen ? 'open' : ''}`}
onClick={handleToggle}
>
<p className="number">{num < 9 ? `0${num + 1}` : num + 1}</p>
<p className="title">{title}</p>
<p className="icon">{isOpen ? '-' : '+'}</p>
{isOpen && <div className="content-box">{text}</div>}
</div>
);
}
Golden Rules:
- Build static first — Create components without state
- Add state where needed — "Does this change?" → useState!
- Each independent item gets its own state — If they operate separately
- Use index for numbering —
map((item, index) => ...) - Format numbers nicely — Add leading zero for single digits
- Conditional rendering with && —
{condition && content} - Conditional classes with template literals — `
class ${condition ? 'extra' : ''}` - Always add key prop — Use unique values from data
- Use callback for state updates —
setState(current => newValue) - Event handlers on the container — Click anywhere on the item to toggle
One Sentence Summary: > "An accordion in React is built by creating a parent Accordion component that maps over an array of data to render multiple AccordionItem child components, where each child has its own independent isOpen state that controls whether its content is visible through conditional rendering, toggled by an onClick event handler that flips the state using the callback pattern, with formatted numbers and conditional styling to provide visual feedback about which items are expanded or collapsed!"