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

48 lines
1.8 KiB
Markdown

---
title: "React useState Dropdown — CSS group-hover vs useState for Playwright"
source: daily/2026-05-10.md
updated: 2026-05-10
---
## Problem
Playwright `hover()` on a trigger element does not reliably open a React dropdown that uses `useState` for visibility:
```tsx
// ❌ 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:
```tsx
// ✅ 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.