react / react/react-native

The case for possibility of triggering synchronous layout from UI thread

Open
#51,815 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs: Triage :mag: p: Software Mansion Partner Type: New Architecture
Dominant language
C++
Stars
127k
Forks
25.3k
Avg merge
1d 23h
Merged PRs (30d)
4

Description

Description

Follow up to #51809.

This issue report is about lack of support for synchronous layout on UI thread, that is necessary to expose / implement certain platform components, e.g. bottom sheets, without "flicker effect"
(see attached recording).

In current state of Fabric, we, in react-native-screens need to go for ugly & not really working workarounds.

To reproduce the problem it is enough to run the application on iOS (the issue applies also to Android)
& drag the sheet upwards.

Please note that the attached 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 - this is also asynchronous and leads to the same effect.

Error mechanism

I'll describe it on iOS example, however the same case can be built for Android.

This is kinda simplified & assumes just one possible order of execution, but should be enough to explain the problem.

Let's consider scenario where we drag the sheet up (see recording to get the context), and the sheet has starting height h1.

  1. User tries to drag the sheet up,
  2. UIKit receives touch & among many things native layout is triggered,
  3. The sheet is resized (height goes up to some h2),
  4. We send asynchronous event (in library code we do an asynchronous shadow node state update),
  5. User continues to drag the sheet up,
  6. Another native sheet resize, sheet has some height h3 now,
  7. React updates state & starts render,
  8. User continues to drag the sheet up,
  9. Another native sheet resize, sheet has some height h4 now,
  10. React render & commit completes & mount is scheduled & let's say executed,
  11. SheetView receives layout computed for the moment sheet had height h2, despite the fact the sheet is already h4.
  12. h4 > h1 - hence the flicker.

I went with sending direct event here, just to simplify the reproduction, but if you need exact setup react-native-screens has, let me know I'll setup custom shadow node for SheetView component.

Motivation

The motivation from #51809 applies.

Additionally, need to be able to do this, e.g. in order to let users implement footer components that pair up with the sheet, or allow for content resizing just as in the reproduction.

Possible solution approach

We need the ability to trigger synchronous state update together with commit, layout & later mount from the UI thread for the high priority UI events, to keep
the interface responsive and in sync with native components, which are crucial for "native app experience".

I know this kinda is behind feature flag
right now & some work is being done around this & together with applying appropriate commit options we would be able to achieve synchronous commit & mount.

Bringing it to your attention to emphasize the need for this feature. At the same time from the perspective of native lib dev want to say that it would be great
to have ability to choose whether state update is synchronous or not (not all updates need to block the UI thread).

Steps to reproduce
  1. Run the app on iOS (the reproduction is set up for that platform)
  2. Drag the sheet up
  3. Observe the 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: 121.70 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/tree/%40kkafar/ui-layout-issue <-- please notice the branch!

Screenshots and Videos

https://github.com/user-attachments/assets/f226ec07-7891-4d7e-a55f-b3c885c73fbd

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the flicker with the mandatory layout-issues branch by running the iOS app and dragging the sheet upward. Then read packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp and the related Fabric commit path. Done means high-priority UI events can opt into synchronous state update, commit, layout, and mount on iOS and Android without the sheet becoming out of sync.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, react-native
Domain
mobile-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.