Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,21 @@ class SandboxReactNativeView(
val w = right - left
val h = bottom - top
for (i in 0 until childCount) {
getChildAt(i).layout(0, 0, w, h)
val child = getChildAt(i)
// The child is a ReactSurfaceView from a SEPARATE ReactHost. Fabric
// lays our view out by calling layout() directly, without a measure
// pass, so the child never receives onMeasure(). But ReactSurfaceView
// only pushes layout constraints to its surface from onMeasure() (and
// its onLayout() is a no-op until wasMeasured is true). Without an
// explicit measure the nested surface keeps its initial constructor
// constraints and renders blank (regression surfaced on RN 0.85).
// Measure with EXACTLY specs so updateLayoutSpecs() runs with our real
// size, then lay the child out to fill us.
child.measure(
MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY),
)
child.layout(0, 0, w, h)
}
}

Expand Down
9 changes: 7 additions & 2 deletions packages/react-native-sandbox/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useRef,
} from 'react'
import type {NativeSyntheticEvent} from 'react-native'
import {StyleProp, StyleSheet, View, ViewProps, ViewStyle} from 'react-native'
import {StyleProp, View, ViewProps, ViewStyle} from 'react-native'

import type {NativeSandboxReactNativeViewComponentType} from '../specs/NativeSandboxReactNativeView'
import NativeSandboxReactNativeView, {
Expand Down Expand Up @@ -305,9 +305,14 @@ const SandboxReactNativeView = forwardRef<
return null
}, [])

// The native sandbox view fills the user-styled wrapper as an in-flow
// flex child. It used to be position:absolute (StyleSheet.absoluteFillObject),
// but on RN 0.85 an absolutely-positioned child with all-zero insets no longer
// stretches to a flex-sized parent — it collapses to height 0 and the embedded
// surface renders blank. flex:1 makes it claim the wrapper's space reliably.
Comment on lines +308 to +312

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be on the same page it work without issueson pre 0.85 too, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried applying fix on android and ios RN80 demo apps:

Ios baseline (android baseline same, just diff backing color)
ios-demo-baseline

ios with fix applied
ios-demo-with-pr38-fix

android with fix applied
android-demo-with-pr38-fix


AI-assisted analysis

Finding: flex: 1 introduces layout regression on pre-0.85 apps (Android + iOS)

The change from StyleSheet.absoluteFillObject to flex: 1 alters how the native sandbox view interacts with its parent wrapper's padding.

Before (absoluteFillObject): The native view stretches to fill the entire wrapper bounds, ignoring any padding set on the wrapper <View style={style}>.

After (flex: 1): The native view participates in normal flex layout and respects the parent's padding, making the sandbox content area narrower/shorter.

Root cause

// In the library's index.tsx:
<View style={style}>          {/* ← user's style, may include padding */}
  <NativeSandboxReactNativeView style={_style} />  {/* ← was absoluteFill, now flex:1 */}
</View>

absoluteFillObject (position absolute + zero insets) ignores the parent's padding.
flex: 1 (in-flow child) respects it. Any consumer with padding on their sandbox component style will see a layout change.

Recommendation

The fix is correct for solving the RN 0.85 zero-height issue, but it's a breaking layout change for existing consumers. Consider one of:

  1. Strip padding from the inner wrapper — apply the user's style but override padding to 0 on the wrapper, or split into an outer container (user style) and inner container (no padding, holds the native view)
  2. Use width: '100%', height: '100%' instead of flex: 1 — this fills the content area explicitly without requiring flex participation, and behaves more like the old absolute approach while still being in-flow
  3. Document as a breaking change — if the old behavior (ignoring padding) was unintended, note it in release notes so consumers can remove padding from their sandbox styles

const _style: StyleProp<ViewStyle> = useMemo(
() => ({
...StyleSheet.absoluteFillObject,
flex: 1,
}),
[]
)
Expand Down