Skip to content

feat: only the last mounted OverlayProvider handles open events#220

Open
manNomi wants to merge 1 commit intotoss:mainfrom
manNomi:feat/last-provider-wins
Open

feat: only the last mounted OverlayProvider handles open events#220
manNomi wants to merge 1 commit intotoss:mainfrom
manNomi:feat/last-provider-wins

Conversation

@manNomi
Copy link
Copy Markdown

@manNomi manNomi commented Feb 27, 2026

When multiple OverlayProvider instances share the same overlay context, all providers previously received open events causing duplicate renders.
Each provider now tracks its mount order and only the most recently mounted instance handles open events, falling back to the previous provider when it unmounts.

🌐 This PR description was written with the help of a translation tool. I apologize in advance if any part of it is unclear or inaccurate — please feel free to ask for clarification!

Description

When using navigation stacks (e.g. Stackflow, React Navigation), mounting multiple OverlayProvider instances caused all providers to subscribe to the same event channel, resulting in duplicate overlay renders on overlay.open().

This PR fixes the issue by maintaining a module-level stack of mounted providers, ensuring only the most recently mounted provider handles open events. When that provider unmounts, the previous one automatically becomes active again.

Related Issue: Fixes # (issue_number)

Changes

  • Added module-level mountedInstances stack to track mounted provider instances
  • Each provider pushes its ID on mount and splices it on unmount
  • The last element in the stack is always the active provider, so the previous provider is automatically restored when the current one unmounts

Motivation and Context

In navigation stack environments, it's common to mount multiple OverlayProvider instances per activity or screen to achieve isolated overlay scopes. However, since all providers shared the same event channel, calling overlay.open() triggered renders in every mounted provider simultaneously.

The only workaround was wrapping experimental_createOverlayContext in a React Context manually, which added boilerplate and relied on an experimental API. This change makes scoped overlay handling work correctly out of the box without any extra setup.

How Has This Been Tested?

  • Manually tested mounting ProviderAProviderB in sequence and verified only ProviderB handles overlay.open()
  • Unmounted ProviderB and verified ProviderA correctly becomes active and handles subsequent overlay.open() calls
  • Verified no duplicate renders occur when multiple providers are mounted simultaneously
  • Tested in a Stackflow-based navigation stack with per-activity providers

Screenshots (if appropriate):

Before: overlay.open() triggers renders in all mounted providers
After: Only the most recently mounted provider renders the overlay

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have performed a self-review of my own code.
  • My code is commented, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • Any dependent changes have been merged and published in downstream modules.

Further Comments

This PR was inspired by #221.
It's intended as a lightweight, directional proposal rather than a complete implementation — I'd really appreciate any feedback or thoughts on whether this is the right approach. If there's a preferred direction or existing plan for handling this, I'd love to know!

Regardless of the implementation approach, I strongly believe that triggering overlay.open() across all mounted providers simultaneously is a bug rather than intended behavior. There is no practical use case where a user would expect a single open() call to render overlays in every provider at once — especially in a navigation stack context where each screen should manage its own overlay scope independently.

Known trade-off: close, unmount, closeAll, and unmountAll still dispatch to all providers, creating an asymmetry with open. If this is a concern, an opt-in prop (e.g. <OverlayProvider exclusive>) could be considered as an alternative approach.

The previous workaround using experimental_createOverlayContext wrapped in a React Context still works and is not affected by this change. This fix addresses the root cause at the provider level so users no longer need to implement the pattern manually.

When multiple OverlayProvider instances share the same overlay context,
all providers previously received open events causing duplicate renders.

Each provider now tracks its mount order and only the most recently
mounted instance handles open events, falling back to the previous
provider when it unmounts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@manNomi manNomi requested a review from jungpaeng as a code owner February 27, 2026 10:55
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 27, 2026

@manNomi is attempting to deploy a commit to the Toss Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 27, 2026

⚠️ No Changeset found

Latest commit: 94f35eb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant