Skip to content

Repro for #57780: removing borderWidth hides children of a clipped rounded View on Android - #57781

Open
SatyamBansal wants to merge 1 commit into
react:mainfrom
SatyamBansal:repro/57780-android-borderwidth-nan-clip
Open

Repro for #57780: removing borderWidth hides children of a clipped rounded View on Android#57781
SatyamBansal wants to merge 1 commit into
react:mainfrom
SatyamBansal:repro/57780-android-borderwidth-nan-clip

Conversation

@SatyamBansal

Copy link
Copy Markdown

Summary:

Reproducer for #57780, as requested by react-native-bot (Needs: Repro). Not intended to be merged — it edits RNTesterPlayground.js per the How to report a bug guidance.

On Android, a View with borderRadius and overflow: 'hidden' stops drawing all of its children once its borderWidth prop is removed — as opposed to being set to 0. The view's own background keeps painting, so the row renders as a correctly sized, correctly rounded, completely empty box. The children remain mounted and laid out (the a11y tree still reports their labels and frames); they just are not drawn. Re-adding borderWidth restores them.

Suspected cause, traced in the issue:

  1. ReactViewManager declares the borderWidth prop group with defaultFloat = Float.NaN, so removing the prop calls the setter with NaN (the parameter is a primitive Float, so it can never be null there).
  2. BackgroundStyleApplicator.setBorderWidth documents null as the removal signal but receives NaN, stores it, and allocates BorderInsets on that same call.
  3. BorderInsets.resolve() is an elvis chain ending in ?: 0f, which only fires on null — so NaN passes through and it returns RectF(NaN, NaN, NaN, NaN).
  4. clipToPaddingBoxWithAntiAliasing subtracts those insets from composite.bounds, producing an all-NaN padding box; with borderRadius set it becomes a Path installed via canvas.clipPath(), which clips every child away.
  5. ReactViewGroup.dispatchDraw only applies that clip when overflow is not visible, which is why overflow: 'hidden' is required. The background survives because background drawables paint in View.draw(), before dispatchDraw().

iOS is unaffected: RCTView uses _borderWidth = -1 as its unset sentinel and clamps with MAX(0, _borderWidth). A negative sentinel is neutralised by arithmetic; NaN propagates.

This pattern is common in "selectable card" UI, where the selected style adds borderWidth: 1 and the unselected style omits it.

Changelog:

[INTERNAL] [CHANGED] - RNTesterPlayground repro for #57780 (not for merge)

Test Plan:

Open RNTester on Android and go to the Playground example. Two groups of three rows:

  1. In group A, tap "Row B" → "Row A" keeps its grey rounded background but its text disappears.
  2. In group B — identical except the base style sets borderWidth: 0 — tap "Row B" → "Row A" keeps its text, as expected.

Group B is the control that isolates the cause. borderWidth: 0 and "no borderWidth at all" produce an identical 1px geometry change, so this is not a layout or measurement problem — only the removed prop triggers it. The broken state is also sticky: scrolling the row out of view and back does not restore the text, only re-adding a borderWidth does.

On iOS, both groups behave correctly.

Verification provenance

To be precise about what I actually ran: I verified this on a fresh @react-native-community/cli init app on 0.86.0, release build, on a Pixel 8 (Android 17, SDK 37, Hermes, New Architecture) — no third-party libraries, no patches to React Native, no ReactNativeFeatureFlags overrides. Four variants were compared there:

overflow: hidden unselected borderWidth Result
yes absent (setter gets NaN) children not drawn
yes 0 explicit correct
yes 1 constant, only colour toggles correct
no absent (NaN) correct

I have not built RNTester from main on Android myself, so this Playground patch is a faithful transcription of that verified reproducer rather than an independently re-run one. What I did check on main is the source: BorderInsets.resolve() still has only the null fallback and no NaN guard, on both v0.86.2 and main, so the defect should still be present.

Suggested fix

Treat NaN as unset in BorderInsets — either ?.takeUnless { it.isNaN() } per edge in resolve(), or normalise NaN to null in setBorderWidth so the stored value matches that function's own documented contract. The latter fixes every consumer of BorderInsets rather than only the clip path.

@meta-cla

meta-cla Bot commented Jul 31, 2026

Copy link
Copy Markdown

Hi @SatyamBansal!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

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