Skip to content

First layout problems with native-layout-driven components #51809

Open
@kkafar

Description

@kkafar

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

  1. Build & run the application on iOS (remember to install pods & run codegen).
  2. 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.

initial-layout-problem-recording.mov

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions