Fixing Per-Card Embed Modals in React — One State, Wrong Video
The Bug & Fix — Documented
What Was Happening
You had one shared state for all cards:
const [isOpen, setIsOpen] = useState(false); // one boolean for EVERYONE
When any card was clicked → isOpen = true → every card rendered its modal at the same time. With 4 projects, you got 4 stacked overlays. The last one in the DOM won — so you always saw the wrong video.
Think of it like this: Imagine a classroom with 30 students, and one light switch controls all their desk lamps. You click one lamp → all 30 turn on.
The Fix
Track which card is open, not just whether something is open:
const [openIndex, setOpenIndex] = useState<number | null>(null);
// null = nothing open
// 0 = first card open
// 2 = third card open
Now when you click card #2:
setOpenIndex(2); // only card 2 knows it's open
And in the render:
{openIndex === index && ( // only THIS card's modal shows
<div className="fixed inset-0 ...">
<iframe src={project.YoutubeEmbed} />
</div>
)}
Same classroom analogy: Now each student has their own switch. Click student 3's lamp → only lamp 3 turns on.
The Empty Embed Problem
Your second project has an empty YoutubeEmbed: "". Without a guard, clicking it opens a modal with a blank/broken iframe.
const hasEmbed = !!project.YoutubeEmbed; // true if URL exists, false if ""
// Only show play button if there's something to play
{hasEmbed && (
<div className="absolute inset-0 flex items-center justify-center ...">
<Play />
</div>
)}
// Only open modal if there's something to show
onClick={() => hasEmbed && setOpenIndex(index)}
The JSX Attribute Problem
HTML and React JSX use different attribute names:
| HTML | React JSX |
|---|---|
frameborder="0" | frameBorder="0" |
allowfullscreen="" | allowFullScreen |
React uses camelCase for all attributes. Writing HTML-style attributes in JSX either silently fails or throws a warning — the iframe may not behave correctly.
Summary
| Problem | Before | After |
|---|---|---|
| Shared state | useState(false) | `useState<number |
| Which modal opens | All of them | Only openIndex === index |
| Empty embed | Broken iframe | hasEmbed guard |
| Attributes | frameborder | frameBorder |