← Back to Blog

Fixing Per-Card Embed Modals in React — One State, Wrong Video

By Amin

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:

HTMLReact 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

ProblemBeforeAfter
Shared stateuseState(false)`useState<number
Which modal opensAll of themOnly openIndex === index
Empty embedBroken iframehasEmbed guard
AttributesframeborderframeBorder