-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Description
Description
This issue describes a change in behavior when UI updates are made from useLayoutEffect
calls between old and new architecture.
The issue surfaced when trying expo router with the new architecture and is due to the code in Screen.tsx
that calls a method that updates a state in useLayoutEffect
hook.
In this scenario, we have two main components being mounted at the same time: a parent and child component. The parent component renders with some default set of attributes, but then the child component uses useLayoutEffect
hook that gets triggered immediately upon mounting to update the parent state and as a consequence make parent re-render with some of the default attributes changed. A simple version of this scenario is implemented in the reproducer app: https://github.com/kmagiera/use-layout-effect-ui-flash/blob/main/ReproducerApp/App.tsx
In the above scenario, with the old architecture, the parent component receives both initial and updates attributes in a single UI transaction, meaning that they will be flushed onto screen in one go and you'll never see the intermediate state before the initial attributes are set and the updates ones are applied.
However, with the new architecture, the two updates come in separate transactions that are scheduled to run on UI thread.
When the two transactions are executed in between frames, you'll see the intermediate UI state flushed briefly as presented on the video attached later on.
Here is a screenshot from the reproducer app that presents three consecutive frames after pressing "toggle" button. In the reproducer app, the parent component renders with red background at first, and then the child component updates the background to blue.
With the old architecture, you never get the frame with red background as all the updates happen in a single UI thread loop run.
Note: I am unsure whether it is a bug or intended behavior. There is a possibility that React or React Native doesn't want to guarantee for such updates to happen in single UI transaction and that expo router shouldn't rely in this side effect. That being said, despite the fact expo router's approach adds an additional render pass, it makes its API much more elegant.
Steps to reproduce
- Clone reproducer app https://github.com/kmagiera/use-layout-effect-ui-flash
- Build fabric version for iOS
- Notice that when pressing "toggle" button the background color of the new element shortly flashes red before turning blue
Notice that this issue doesn't happen when app is running on the old architecture.
The reproducer app uses ALotOfViews
components in order to make the bug reproduce more reliably. The same issue happens even without these additional views, but it reproduces only a fraction of times from my testing. The root cause of the problem seem to be that finalizeUpdates
gets called twice, while on Paper, both updates happen in the same UIManager batch.
Another way to reproduce the issue is to remove ALotOfViews
component and put breakpoint in the finalizeUpdates
method and notice that once it hits the breakpoint for the second time, the view is already created and its background color is red. On paper, you can add didSetProps
to RCTView
and observe it gets triggered only once after receiving both background color updates.
React Native Version
0.73.6
Affected Platforms
Runtime - iOS
Runtime - Android
Areas
Fabric - The New Renderer
Output of npx react-native info
info Fetching system and libraries information...
System:
OS: macOS 13.5.2
CPU: (10) arm64 Apple M2 Pro
Memory: 81.53 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 18.17.1
path: ~/.nvm/versions/node/v18.17.1/bin/node
Yarn:
version: 1.22.21
path: ~/.nvm/versions/node/v18.17.1/bin/yarn
npm:
version: 9.6.7
path: ~/.nvm/versions/node/v18.17.1/bin/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.15.2
path: /Users/mdk/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 23.2
- iOS 17.2
- macOS 14.2
- tvOS 17.2
- visionOS 1.0
- watchOS 10.2
Android SDK: Not Found
IDEs:
Android Studio: 2023.2 AI-232.10300.40.2321.11567975
Xcode:
version: 15.2/15C500b
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.9
path: /usr/bin/javac
Ruby:
version: 3.3.0
path: /Users/mdk/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.74.0-rc.9
wanted: 0.74.0-rc.9
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
n/a
Reproducer
https://github.com/kmagiera/use-layout-effect-ui-flash
Screenshots and Videos
Reproducer app running on fabric (see red background flashes):
fabric.mp4
Reproducer app running on Paper (no red background flashes):