feat(headless): hold Popover contents while it closes - #9310
Merged
alexcarpenter merged 1 commit intoJul 31, 2026
Conversation
The popup outlives `open` by the length of its exit animation, and whatever closed it has usually changed the data behind it, so the contents swapped under the animation. Wrap the popup's children in a new `Freeze` utility: a Suspense boundary plus a child that throws a never-settling promise, so React keeps rendering the subtree but holds the commit until the popover reopens. The popup element itself stays live, so `data-closed` / `data-ending-style` still land.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 5d76bdc The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Stacked on #9184 (
carp/account-button-switcher).A popover's popup outlives
openby the length of its exit animation, and whatever closed it has usually changed the data behind it — switching account or organization from the UserButton closes the menu and you watch its contents swap while it animates out.Popover.Popupnow wraps its children in a newFreezeutility (@clerk/headless/utils): a Suspense boundary plus a sibling that throws a never-settling promise, so React keeps rendering the subtree but holds the commit. The DOM keeps painting its last frame untilfrozenflips back off, at which point the pending render commits. The popup element itself is outside the freeze, sodata-closed/data-ending-stylestill land and the exit animation is unchanged.The technique is from https://barvian.me/react-exit-animations, adapted for React 18, which this package still supports (
react: ^18.0.0 || ~19.0.3 || …):React.use(infinitePromise)→throw never.display: contentswrapper the component owns. React hides a suspended boundary's host children withdisplay: none !important; an insertion effect restoresdisplay: contentson the same commit, before paint. React also detaches refs of hidden children, so the wrapper's node is captured through a ref callback that ignoresnullrather than read back from a plain ref.Trade-offs, both documented in the popover README:
display: contentselement between the popup and its content.nullwhile the popover is closing, until it reopens.Freezeis unit tested. The popover wiring is not covered end to end: happy-dom has no animations, somountedflips tofalseimmediately on close and there is no exit window to assert against.BEFORE
before.mov
AFTER
after.mov
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change