wix / wix/react-native-navigation

Custom TopBar button (react component) never resizes to content width under Fabric on iOS 26 and resets to 44pt after any button rebuild

Open
#8,339 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

platform: iOS type: accepted/bug
Dominant language
MDX
Stars
13.2k
Forks
2.6k
Avg merge
6d 2h
Merged PRs (30d)
1

Description

What happened?

On iOS 26 with the TopBar (RNNReactButtonView/RNNUIBarButtonItem's initWithCustomView: code path, gated behind if (@available(iOS 26.0, *))), a custom React component used as a topBar.rightButtons[].component never grows past the reserved 44pt minimum slot, even though the button is clearly designed to support it (see the kMinBarButtonSlotSize / "grow width once when React reports intrinsic size" comments in RNNUIBarButtonItem.mm and RNNReactButtonView.mm).

Image

Tracked this down to two separate, compounding bugs in the current implementation:

  1. The width-growth mechanism never fires under Fabric (New Architecture).
    The whole feature depends on -surface:didChangeIntrinsicSize: being called on RNNReactButtonView's delegate. That callback is only ever invoked by the legacy, non-Fabric RCTSurface (see RCTSurface.mm, setIntrinsicSize:[delegate surface:self didChangeIntrinsicSize:...], driven by rootShadowView:didChangeIntrinsicSize:). Under the New Architecture, RCTFabricSurface declares the same intrinsicSize property (inherited from RCTSurfaceProtocol) but never sets it and never calls the delegate method at all — there is no matching code path in RCTFabricSurface.mm. As a result, RNNReactButtonView.intrinsicSize/measuredSurfaceContentSize is always {0,0} and the button's width constraint is never updated from its initial 44pt value, regardless of how wide the mounted React content actually is.

  2. Even after working around (1), the button snaps back to 44pt the next time TopBar buttons are rebuilt (e.g. reselecting a bottom tab, or any mergeOptions/appear-triggered re-application of topBar.rightButtons). RNNButtonsPresenter.setButtons: calls RNNButtonBuilder.build:initWithCustomView: and constructs a brand new RNNUIBarButtonItem every time buttons are applied, even though the underlying RNNReactButtonView (looked up via RNNReactComponentRegistry.createComponentIfNotExists:) is reused (it's keyed by componentId in a map and only replaced/removed when actually different). initWithCustomView: unconditionally creates new UILayoutPriorityRequired width/height constraints on that reused view every single time, without ever deactivating the constraints created by the previous RNNUIBarButtonItem instance. This leaves multiple conflicting required-priority width constraints active on the same view (e.g. one pinned at the previously-grown width, a fresh one reset to 44pt) — Auto Layout can't satisfy both, and in practice the newly-added 44pt constraint wins, visibly snapping the button back down.

What was the expected behaviour?

A custom React component used as a TopBar button should size itself to its actual rendered content width (bounded below by the ~44pt minimum tap-target size), and should keep tracking that width correctly:

  • On initial mount, based on the component's real rendered content, not just the reserved 44pt placeholder.
  • On subsequent content changes within the same mounted component (e.g. a counter/badge whose text gets longer or shorter).
  • Consistently across repeated TopBar button re-applications (e.g. switching bottom tabs away and back), without the width getting reset or fighting with stale constraints from a previous RNNUIBarButtonItem instance.
Was it tested on latest react-native-navigation?
  • I have tested this issue on the latest react-native-navigation release and it still reproduces.
Help us reproduce this issue!

Reproduction steps

  1. Clone this public minimal reproduction repo:
    https://github.com/matyi/RNNGlassRepro

  2. Run the app and open the screen that uses a custom React component in topBar.rightButtons.

  3. Observe that the button renders variable-width content, but its width stays constrained and the content gets clipped instead of expanding to fit.

  4. As a workaround, manually measure the mounted view hierarchy and pass the actual size through handleIntrinsicSizeChange:. With that fallback in place, the button grows correctly on first mount.

  5. Put the screen inside a bottomTabs layout.

  6. Let the button grow, then switch to another tab and back, or trigger any flow that causes topBar.rightButtons to be re-applied, such as a mergeOptions call in componentDidAppear.

  7. Observe that the button width resets to the minimum size and clips the content again, even though the button content itself has not changed.

In what environment did this happen?
  • React Native Navigation version: 8.8.11
  • React Native version: 0.81.x (or even the lates 0.86.x)
  • Has Fabric (React Native's new rendering system) enabled: yes
  • Node version: v22.19.0
  • Device model: reproduced on iOS 26.1 simulator, also iphone 15 ios 26.5 real device
  • iOS version: 26.1, 26.5

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with RCTFabricSurface.mm and compare its intrinsic-size handling with RCTSurface.mm, then trace the delegate path through RNNReactButtonView.mm. Read RNNUIBarButtonItem.mm and RNNButtonsPresenter.setButtons: to follow constraint creation and button rebuilding. Done means content-driven widths work under Fabric, update when content changes, and remain stable after repeated TopBar re-application.

Written by the indexing model from the issue text.

Assessment

Tech stack
objective-c, react-native
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.