-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UI updates made from layout effect are flushed in separate UI transaction #44111
Comments
|
I just tested this on Android and there's also similar difference on Android with and w/o Fabric. Attaching video from Android with fabric where you can also see red background flashes: |
Thanks for the extremely detailed report @kmagiera So there are a number of things at play here. We're aware that The reason behind this is that in order for Those changes have now landed on However, I've tested it and I've realized the current implementation is broken, and we'll have to fix it (thanks to your report we were able to spot this early on!). I'm coordinating with @sammy-SC on how we can fix this thing forward, and ideally have a nightly version where I haven't fully investigated why it seems to work correctly on old arch as |
…nabled Summary: When using `batchRenderingUpdatesInEventLoop` the Fabric Differentiator may more a newly created ShadowNode and subsequent update (eg coming from a layout effect) into a single MountingTransaction. This enables us to correctly implement layoutEffect in the new architecture and prevent flickering of intermediate states. On Android, we also have a view preallocation mechanism, which will create native views for ShadowNodes as they're created by React. This mechanism is incompatible with `batchRenderingUpdatesInEventLoop` as we will ignore the `create` instruction in the MountingTransaction since we already have a preallocated view. However the props we used for preallocation will be outdated/different compared with the ones in the `MountingTransaction`, which represents a severe regression to the issue described in facebook#44111. Changelog: [Android][Fixed] Mutations to newly created views from useLayoutEffect were not always applied. Differential Revision: D56301318
…nabled (facebook#44144) Summary: When using `batchRenderingUpdatesInEventLoop` the Fabric Differentiator may more a newly created ShadowNode and subsequent update (eg coming from a layout effect) into a single MountingTransaction. This enables us to correctly implement layoutEffect in the new architecture and prevent flickering of intermediate states. On Android, we also have a view preallocation mechanism, which will create native views for ShadowNodes as they're created by React. This mechanism is incompatible with `batchRenderingUpdatesInEventLoop` as we will ignore the `create` instruction in the MountingTransaction since we already have a preallocated view. However the props we used for preallocation will be outdated/different compared with the ones in the `MountingTransaction`, which represents a severe regression to the issue described in facebook#44111. Changelog: [Android][Fixed] Mutations to newly created views from useLayoutEffect were not always applied. Differential Revision: D56301318
Hey all, As mentioned in my previous update, the So if you test your reproducer against
For Android the situation is more complicated, as we realized that view preallocation is conflicting with the recent changes that were supposed to fix |
hey folks, I just wanted to post the videos I've previously shared with you on other platforms for reference on this issue for the broader audience to understand how this issue might manifest in their apps. New arch enabledScreen_Recording_2024-04-04_at_12.12.44_PM.movOld arch enabledScreen_Recording_2024-04-04_at_12.18.11_PM.mov |
Closing as this has been fixed for good in 0.74.1 🎉 |
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 inuseLayoutEffect
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.tsxIn 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
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 thatfinalizeUpdates
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 thefinalizeUpdates
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 adddidSetProps
toRCTView
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
Stacktrace or Logs
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):
paper.mp4
The text was updated successfully, but these errors were encountered: