Skip to content

fix: skip Fabric SafeAreaView state updates while detached from window#735

Merged
janicduplessis merged 1 commit into
mainfrom
@janic/fabric-skip-detached-inset-updates
Jul 2, 2026
Merged

fix: skip Fabric SafeAreaView state updates while detached from window#735
janicduplessis merged 1 commit into
mainfrom
@janic/fabric-skip-detached-inset-updates

Conversation

@janicduplessis

Copy link
Copy Markdown
Collaborator

Summary

Skip updateStateIfNecessary on the Fabric SafeAreaView while the view has no window. didMoveToWindow re-runs the update on reattachment, so inset changes that happen while a screen is hidden still propagate.

Why: when a react-native-screens native stack pushes a screen, it detaches the covered screen from the window. didMoveToWindow fires with window == nil, and when the nearest provider sits outside the screen, findNearestProvider can't reach it through the severed superview chain and falls back to self, which reports wrong safeAreaInsets (in my repro the top inset dropped 116 → 0). That gets committed as Fabric state for the hidden screen: it can briefly render under the notch on pop-back until the corrective commit lands, and pays wasted shadow-tree commits while covered.

Instrumented log from the example app (native-stack Details screen resolving to the root provider; push a second Details, pop back):

commit provider=0x10d06a080    insets={116, 0, 34, 0}  window=attached   <- mount
commit provider=SELF-FALLBACK  insets={0, 0, 34, 0}    window=NIL        <- bogus commit for hidden screen
commit provider=0x10d06a080    insets={116, 0, 34, 0}  window=attached   <- corrective commit on reattach

The Paper implementation already guards this (findNearestProvider returns nil and invalidateSafeAreaInsets early-returns), and the Fabric provider got the equivalent guard in #629.

Test Plan

Example app on an iPhone 17 simulator (Fabric, RN 0.85, react-native-screens 4.25), Native Stack example, with the Details screen resolving to the root provider (per-screen provider temporarily removed) to trigger the severed-chain path:

  • Without the fix: the log above — a bogus window=NIL commit for the detached screen on every push/pop cycle.
  • With the fix, identical flow: zero window=NIL commits, and insets are correct immediately after pop (top: 116, bottom: 34).
  • Inset changes while hidden still propagate: rotated to landscape while Details was covered, popped back → the reattached screen showed the correct new insets ({78, 62, 20, 62}).
  • Caveat: the visible flash doesn't reproduce on the simulator — the corrective commit lands within the same frame there. On slower devices it can land a frame late, which is the reported symptom; the eliminated window=NIL commits are the reliable evidence either way.

When react-native-screens native-stack covers a screen it detaches the
screen from the window. The detached SafeAreaView can then read zero
safeAreaInsets and commit a zero-inset Fabric state update for the
hidden screen, which shows up as a flash of content under the notch or
home indicator on pop-back, plus wasted shadow-tree commits for hidden
screens. Skip updateStateIfNecessary while the view has no window,
mirroring the provider-side superview guard. didMoveToWindow re-runs
the update on reattachment, so inset changes that happen while the
screen is hidden still propagate.
@janicduplessis janicduplessis marked this pull request as ready for review July 2, 2026 03:37
@janicduplessis janicduplessis merged commit ae0e83f into main Jul 2, 2026
4 checks passed
@janicduplessis janicduplessis deleted the @janic/fabric-skip-detached-inset-updates branch July 2, 2026 03:38
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