Conversation
On web the provider always renders a wrapping View, which is not always wanted, for example when the provider is mounted at the root of an app by ExpoRoot. Add an undocumented, unstable opt out that renders children directly instead. Since there is no view to measure when the wrapper is gone, insets and frame fall back to window values, which is the existing behaviour of the branch that runs when the provider element cannot be measured. For the same reason style and the ref added in appandflow#775 have no element to apply to, so ref stays null while the prop is set. The prop is web only. The default path and every other platform are unchanged.
giaBaoJS
force-pushed
the
feat/unstable-disable-view-on-web
branch
from
September 17, 2026 13:38
2b50026 to
f733f1c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #637
Summary
Context
On web
SafeAreaProvideralways renders a wrappingView, which consumers don't always want - #637 ran into this becauseExpoRootmounts the provider at the root of the app.@satya164 pointed out that switching to a fragment unconditionally is not safe:
and @janicduplessis settled the API:
So this PR is exactly that prop and nothing else: opt-in, undocumented, web-only.
Change
src/NativeSafeAreaProvider.web.tsx:125returns<>{children}</>instead of theViewwhenunstable_disableViewOnWebis set. The prop is declared onNativeSafeAreaProviderPropsandSafeAreaProviderProps; both of those additions are type-only and compile away.No README or docs entry, per "undocumented". There are no other
unstable_props in the repo, so there was no existing convention to follow.Behaviour worth your call
Since #737 the web provider measures its own
Viewto report the frame and to clamp insets. With the wrapper gone there is nothing to measure, so metrics fall back to the window. That is not new code - it is the existingcanMeasureProviderElement === falsepath (src/NativeSafeAreaProvider.web.tsx:47-50, fallback values at:65-70), already exercised by the "falls back to window metrics when ResizeObserver is not available" test. For the root-provider case in the issue, window metrics are the right answer anyway.I deliberately did not try to preserve per-view measurement without a view - that would need a different design (e.g. measuring the parent element) and is more than an unstable escape hatch should carry.
styleis likewise ignored when the prop is set, since there is no element to apply it to.The same now applies to the
refadded in #775: with noViewthere is nothing forsetRefto receive, so a consumer's ref staysnullwhile the prop is set. All three are called out in the prop's doc comment, and the tworeftests below pin the behaviour in both directions.The diff stays out of the measurement effect entirely (only the props destructure and the return statement change), so it should compose cleanly with anything else in flight in that file.
Test Plan
Tests
Six tests added to the existing jsdom suite in
src/__tests__/NativeSafeAreaProvider.web-test.tsx:wraps children in a view by default- asserts the rendered DOM is<div><span id="child"></span></div>, i.e. the wrapper is still there when the prop is not set.renders children without a wrapping element when enabled- asserts the DOM is<span id="child"></span>, no wrapper.attaches ref to the wrapping view by default- thereffrom feat: expose ref to the underlying View #775 is still populated when the prop is not set.leaves ref null when enabled, since there is no view to attach- the documented consequence of dropping the wrapper.reports window insets and frame when enabled- withgetBoundingClientRectmocked to a small rect, metrics still come out as the window insets/frame, proving the fallback path is taken rather than something else being measured.keeps reporting window metrics on resize when enabled- insets and frame still update onresizewith the wrapper gone.The second and fifth together cover the user-visible claim: no wrapper element in the DOM, and insets still resolve.
Verification
yarn validate:jest: 5 suites, 31 tests, 11 snapshots, green. Baseline onmainis 25 tests, 11 snapshots.src/__tests__/__snapshots__/is untouched in this diff, so the native render path (RNCSafeAreaProvider) produces exactly the same tree as before.src/NativeSafeAreaProvider.web.tsxand re-running the suite fails all four "when enabled" tests (the wrapper<div>reappears, the ref is attached, and metrics come from the mocked element rect instead of the window) and passes both default-behaviour tests, as expected.yarn prepare, the only emitted runtime JS containingunstable_disableViewOnWebislib/{module,commonjs}/NativeSafeAreaProvider.web.js. CompiledSafeAreaContext.jscontains zero references - the interface addition is erased.ios/,android/,common/,src/specs/,NativeSafeAreaProvider.tsxandNativeSafeAreaProvider.windows.tsxare all unchanged.yarn validate:eslint(0 errors),yarn validate:typescriptandyarn format:prettier:checkall pass.Rebase
Rebased onto
mainat 5.10.0. The only upstream change that touched this file is #775, which added therefprop; the conflict was the props destructure and the return statement, and both sides are kept. Therefinteraction described above and its two tests are the only content added since the first push.Known risk
You mentioned re-evaluating this in the next major. If a major is close, feel free to park this - it's deliberately small and self-contained so it costs nothing to defer or drop.