obsidian/wiki/concepts/react-state-playwright-css-hover.md
2026-05-10 21:24:49 +01:00

1.8 KiB

title source updated
React useState Dropdown — CSS group-hover vs useState for Playwright daily/2026-05-10.md 2026-05-10

Problem

Playwright hover() on a trigger element does not reliably open a React dropdown that uses useState for visibility:

// ❌ Playwright hover misses this
const [open, setOpen] = useState(false)
<button onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
  Menu
</button>
{open && <DropdownMenu />}

Root cause: useState updates are asynchronous — they schedule a re-render. By the time Playwright checks for the dropdown element, the render cycle may not have completed, so getByRole('menu') returns nothing.

Fix — CSS group-hover:

Replace JS state with a pure-CSS hover using Tailwind's group / group-hover: utilities:

// ✅ Playwright hover works reliably
<div className="group relative">
  <button>Menu</button>
  <div className="hidden group-hover:block absolute top-full left-0">
    <DropdownMenu />
  </div>
</div>

The dropdown is always in the DOM; group-hover:block just changes display. No render cycle, no timing issues — Playwright sees the element immediately after hover.

Secondary Benefit

CSS hover-driven dropdowns also fix the overflow: hidden clipping issue — the dropdown is rendered at the right DOM level and can be placed outside the clipping ancestor. See wiki/concepts/overflow-hidden-clips-absolute-children.

When useState Is Still Needed

  • Keyboard accessibility (Escape to close, focus trapping)
  • Mobile (no hover events) with click-to-toggle
  • Complex open/close animations that require knowing state

In those cases, add an explicit await page.waitForSelector('[role=menu]') in Playwright tests rather than relying on hover timing.