Description
Description
This is a reproduction for the "initial layout" problem.
To reproduce it (the problem), it is enough to build & run the application. The issue occurs in first few frames
of the application runtime. Basically user can observe the undesired layout jump, which can ruin the experience, especially with heavier apps.
Quick technical breakdown
This is simple.
ContentView
layout depends on information retrieved during the first native layout. In current architecture, the first native layout
comes after first mounting completes (the host three is only then created), that is after first commit and React Native layout pass.
Only after the first native layout, we send an asynchronous event from native view to corresponding element, providing it with necessary
information to correct its layout.
This gives no opportunity to supplement the first Yoga layout with information on layout of platform-layout-driven components, leading
to such content jumps as presented on attached recording.
This reproduction is minimal - in sense it isn't exactly what we're doing in react-native-screens
.
We're not sending an event through event emitter, but rather we update the component's shadow node state, but this is also asynchronous and leads to the same effect.
Motivation
Bringing real native, platform-specific experience requires usage of platform-dedicated framework components, that are used widely across
all native apps. This is e.g. what react-native-screens
tries to achieve
by exposing native navigation primitives. These, however, feel & behave native due to being driven by native layout behaviours encoded into
platform frameworks. I, therefore believe that we should have a way to integrate these natively-laid-out components to RN app w/o the described
issue.
So, to reiterate on the problem: we want to lay out our content view (ContentView
in reproduction) using some information
retrieved during native layout. This is very often the case, when using platform-specific primitives, layout of which
is often OS version (and even device) dependent, e.g. navigation bar (header), tab bar, some side panels, even display insets, etc.
Possible solution approach
It seems this could be achieved by opportunity to trigger synchronous state update from UI thread, leading to synchronous commit & mount.
This would be awesome & enable us to solve couple more issues (I'm intending to report them soon).
One thing here to take notice of, is whether first layout does not need to be special - I'm worried about concurrency problems here, but I
feel like we can discuss this further in the comments.
Steps to reproduce
- Build & run the application on iOS (remember to install pods & run codegen).
- Notice the initial flicker.
React Native Version
0.79.2
Affected Platforms
Runtime - Android, Runtime - iOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 15.5
CPU: (14) arm64 Apple M4 Pro
Memory: 130.42 MB / 24.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 23.7.0
path: /opt/homebrew/bin/node
Yarn:
version: 1.22.19
path: /opt/homebrew/bin/yarn
npm:
version: 10.9.2
path: /opt/homebrew/bin/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.16.2
path: /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.4
- iOS 18.4
- macOS 15.4
- tvOS 18.4
- visionOS 2.4
- watchOS 11.4
Android SDK:
API Levels:
- "21"
- "29"
- "30"
- "31"
- "32"
- "33"
- "34"
- "35"
Build Tools:
- 30.0.2
- 30.0.3
- 31.0.0
- 33.0.1
- 34.0.0
- 35.0.0
System Images:
- android-29 | Google Play ARM 64 v8a
- android-31 | Google Play ARM 64 v8a
- android-33 | Google Play ARM 64 v8a
- android-34 | Google APIs ARM 64 v8a
- android-34 | Google Play ARM 64 v8a
- android-35 | Google APIs ARM 64 v8a
- android-35 | Google Play ARM 64 v8a
- android-35 | Pre-Release 16 KB Page Size Google Play ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2024.2 AI-242.23726.103.2422.13016713
Xcode:
version: 16.3/16E140
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.2
path: /usr/bin/javac
Ruby:
version: 2.6.10
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli":
installed: 18.0.0
wanted: 18.0.0
react:
installed: 19.0.0
wanted: 19.0.0
react-native:
installed: 0.79.2
wanted: 0.79.2
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
N/A
MANDATORY Reproducer
https://github.com/kkafar/layout-issues
Screenshots and Videos
Note
Highly recommend to watch this frame by frame. The effect is quick here, since there are not a lot of things to render & layout. Much more visible with heavier layout passes.