Skip to content

fix: differentiate host layers by id and self register - #3862

Merged
isekovanic merged 1 commit into
developfrom
fix/modal-ctx-menu-usage
Oct 16, 2026
Merged

isekovanic merged 1 commit into
developfrom
fix/modal-ctx-menu-usage

Conversation

@isekovanic

@isekovanic isekovanic commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

🎯 Goal

Fixes #3860.

Linear ticket: https://linear.app/stream/issue/RN-444/issues-with-chat-overlays-in-modals

A chat rendered inside a native modal (for example a react-navigation fullScreenModal) needs its own OverlayProvider, because on iOS the root one is rendered in a different view controller, behind the modal. With a second OverlayProvider mounted, the message overlay of the root chat breaks once the modal is closed.

Every MessageOverlayHostLayer shares two pieces of state. One is the shared value controller in message-overlay-store.ts. The other is the set of portal host names (message-overlay, top-item, bottom-item and the closing slots), which react-native-teleport keeps in a single native registry per process. In both cases the last registration wins, and unregistering removes the entry instead of restoring the previous one. After the modal's provider unmounted, the root provider was left without a controller and without registered hosts.

This PR makes sure several OverlayProviders mounted at the same time work, without changing the existing public API.

πŸ›  Implementation details

  • The shared value controller is now a stack. registerOverlaySharedValueController keeps its signature, and unregistering hands control back to the previously registered controller. It's pretty much the same "trick" used for the PortalWhileClosingView stack.
  • Every OverlayProvider creates an id and registers its MessageOverlayHostLayer on a host layer stack. The most recently mounted layer is the top one and owns the overlay.
  • A host layer that is not the top one stays mounted and treats the overlay as inactive, so it renders no backdrop, adds no back handler and never finalizes the close. It parks its portal hosts under names unique to it (<name>:<layerId>, closing slots included) and takes the shared names back once it becomes the top one again. The hosts are renamed instead of unmounted, because remounting them lets Fabric reuse a recycled PortalHostView, which might keep stale sizes. Of course we could work around this and still let stuff remount but it's also arguably breaking since we have configurable parts of the DOM tree in there (and those parts would start remounting rather than just rerendering).
  • A custom MessageActions renders its own hosts with the shared names, so only the top layer renders it.
  • PortalWhileClosingView reads the id of its provider from a new internal OverlayHostLayerContext and only measures and teleports while that provider is the top one.

With a single OverlayProvider the stack has one entry and every code path should behave exactly as before.

Note: This entire process is meant to solve specifically usecases like modals, which fire up their own ModalViewController instance and essentially break. Due to the limitations of refcounting providers in order to determine their ID, having something like 2 OverlayProviders side by side would not really work (whichever one gets registered later wins). This fixable, but of course breaking as the api itself would have to be tied to a dedicated OverlayProvider (and could no longer be used from the direct exports). It is something I'll consider for V10, if there is time but otherwise we'll stick to this for the time being.

🎨 UI Changes

iOS
Before After
Android
Before After

πŸ§ͺ Testing

/

β˜‘οΈ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@Stream-SDK-Bot

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 2033 KB 2036 KB +2912 B 🟑
ai_components_size 130 KB 130 KB 0 B 🟒

@isekovanic
isekovanic merged commit 88c8e53 into develop Oct 16, 2026
12 checks passed
@isekovanic
isekovanic deleted the fix/modal-ctx-menu-usage branch October 16, 2026 10:39
@github-actions github-actions Bot mentioned this pull request Oct 19, 2026
6 tasks
@stream-ci-bot

Copy link
Copy Markdown
Contributor

πŸŽ‰ This PR is included in version 9.10.1 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

πŸ”₯[πŸ›] Issues with chat overlays in modals

4 participants