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
Nobody has claimed this yet.
- 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).
Tracked this down to two separate, compounding bugs in the current implementation:
-
The width-growth mechanism never fires under Fabric (New Architecture).
The whole feature depends on-surface:didChangeIntrinsicSize:being called onRNNReactButtonView's delegate. That callback is only ever invoked by the legacy, non-FabricRCTSurface(seeRCTSurface.mm,setIntrinsicSize:→[delegate surface:self didChangeIntrinsicSize:...], driven byrootShadowView:didChangeIntrinsicSize:). Under the New Architecture,RCTFabricSurfacedeclares the sameintrinsicSizeproperty (inherited fromRCTSurfaceProtocol) but never sets it and never calls the delegate method at all — there is no matching code path inRCTFabricSurface.mm. As a result,RNNReactButtonView.intrinsicSize/measuredSurfaceContentSizeis 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. -
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 oftopBar.rightButtons).RNNButtonsPresenter.setButtons:callsRNNButtonBuilder.build:→initWithCustomView:and constructs a brand newRNNUIBarButtonItemevery time buttons are applied, even though the underlyingRNNReactButtonView(looked up viaRNNReactComponentRegistry.createComponentIfNotExists:) is reused (it's keyed by componentId in a map and only replaced/removed when actually different).initWithCustomView:unconditionally creates newUILayoutPriorityRequiredwidth/height constraints on that reused view every single time, without ever deactivating the constraints created by the previousRNNUIBarButtonIteminstance. 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
RNNUIBarButtonIteminstance.
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
-
Clone this public minimal reproduction repo:
https://github.com/matyi/RNNGlassRepro -
Run the app and open the screen that uses a custom React component in topBar.rightButtons.
-
Observe that the button renders variable-width content, but its width stays constrained and the content gets clipped instead of expanding to fit.
-
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.
-
Put the screen inside a bottomTabs layout.
-
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.
-
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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