You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Android] Dragging or settling a sheet lays out its React content again on every frame #997
On Android, while a sheet is dragged or settling, handleSlide calls updateStateDimensionsForPosition(sheetView.top) on every frame. Each new height is a Fabric state update, so React Native lays out the sheet's whole content tree again and mounts UPDATE_LAYOUT items on the main thread. With a long list in the sheet, that's several milliseconds per frame. The settle animation runs on the same thread, at 120 Hz on a Pixel 8, which leaves 8.3 ms per frame, so quick drags and flicks stutter.
Measured with atrace on a Pixel 8: six quick flicks up and down a sheet with detents [0.45, 1] holding a list of about 20 rows.
Per-frame resize (now)
Resize once per gesture
Main-thread frames over 8.3 ms
56
20
90th-percentile main-thread frame
6.1 ms
3.1 ms
The remaining slow frames are one as each drag starts and one as each settle ends.
I understand the per-frame height keeps a footer at the visible bottom of the sheet. For sheets without a footer, the content could be as tall as the sheet can get while it moves, and be sized to its detent once it settles, which handleStateSettled already does:
if (interactionState isInteractionState.Dragging|| pendingDetentIndex >=0) {
if (containerView?.footerView ==null) {
setStateDimensions(getStateWidth(), realScreenHeight - topInset)
} else {
updateStateDimensionsForPosition(sheetView.top)
}
}
That's two layouts per gesture, not one per frame. Content beyond the bottom of the screen isn't visible while the sheet moves, so it looks the same. Would you take this, or would you rather have it behind a prop, or handled another way? Happy to open a PR either way.
@philiplindberg this is the whole point of v4, synchronous layout while sheet is being resized so flex works as-is like a regular RN view. If there's a real bug, please create a repro and also feel free to open up a PR for me to test.
Thanks, that makes sense. Here's a reproduction: https://github.com/philiplindberg/true-sheet-repros (screen "#997 Layout on every frame"). Expo SDK 58, React Native 0.88, new architecture, 4.0.0 with no patches. The sheet has detents [0.45, 1] and a FlatList of 20 rows wired up through scrollableRef.
scripts/measure.ts flicks the sheet up and down 12 times with adb shell input swipe while atrace runs, and reads the app's main-thread Choreographer#doFrame slices for the frames it drew. Pixel 8, Android 17, 120 Hz, release build, three runs each:
4.0.0
Sized once per gesture without a footer
Drawn frames that lay out the content
263–273 of 275–285
12 of 301–308
Main thread per drawn frame, median
3.4–3.9 ms
1.3 ms
Main thread per drawn frame, 90th percentile
5.0–5.7 ms
2.6–2.9 ms
Drawn frames over 8.3 ms
10–11
7–11
Dropped frames
51–55
25–33
So in a minimal sheet the per-frame layout costs about 2–3 ms of the 8.3 ms budget on every frame of a drag or settle, and about half the dropped frames go with it. The frames over budget don't fall, because the one layout left per gesture takes 10–17 ms. The numbers in the issue came from our app, where the content is heavier. This repro is the one to go by.
You're right about flex. The repro has a "Show bottom bar" toggle that puts a bar below the list, laid out with flex. With the change from the issue applied, the bar sits below the screen while the sheet is dragged, and only comes back when it settles. So this shouldn't be the default.
Our case is a list sheet over a map, resting at 0.45, which people flick between that and full height all the time. Its content never depends on the sheet's height, so it pays for the per-frame layout without using it. Would you take it as an opt-in prop, something like layoutWhileDragging={false}, for sheets like this? I can open a PR with whatever name you prefer.
I'm closing this as intended behavior and it is consistent on all platforms. The views are resized natively and JS gets the update normally, I don't see anything wrong with it since this is a supported behavior with yoga. react-native-screens does this as well on their formSheets.
Activity
github-actions commentedon Oct 29, 2026
Hey @philiplindberg! Thanks for opening the issue.
It looks like your issue is missing a valid reproduction link.
A minimal reproduction helps us investigate and fix the issue faster. Without one, we may not be able to help.
Please provide one of the following:
See "How to create a Minimal, Reproducible Example" for more guidance.
You can edit your original issue or leave a comment with the repro link. Issues without reproductions may be closed after 14 days.
lodev09 commentedon Oct 30, 2026
@philiplindberg this is the whole point of v4, synchronous layout while sheet is being resized so flex works as-is like a regular RN view. If there's a real bug, please create a repro and also feel free to open up a PR for me to test.
philiplindberg commentedon Oct 30, 2026
Thanks, that makes sense. Here's a reproduction: https://github.com/philiplindberg/true-sheet-repros (screen "#997 Layout on every frame"). Expo SDK 58, React Native 0.88, new architecture, 4.0.0 with no patches. The sheet has detents
[0.45, 1]and aFlatListof 20 rows wired up throughscrollableRef.scripts/measure.tsflicks the sheet up and down 12 times withadb shell input swipewhileatraceruns, and reads the app's main-threadChoreographer#doFrameslices for the frames it drew. Pixel 8, Android 17, 120 Hz, release build, three runs each:So in a minimal sheet the per-frame layout costs about 2–3 ms of the 8.3 ms budget on every frame of a drag or settle, and about half the dropped frames go with it. The frames over budget don't fall, because the one layout left per gesture takes 10–17 ms. The numbers in the issue came from our app, where the content is heavier. This repro is the one to go by.
You're right about flex. The repro has a "Show bottom bar" toggle that puts a bar below the list, laid out with flex. With the change from the issue applied, the bar sits below the screen while the sheet is dragged, and only comes back when it settles. So this shouldn't be the default.
Our case is a list sheet over a map, resting at
0.45, which people flick between that and full height all the time. Its content never depends on the sheet's height, so it pays for the per-frame layout without using it. Would you take it as an opt-in prop, something likelayoutWhileDragging={false}, for sheets like this? I can open a PR with whatever name you prefer.github-actions commentedon Oct 30, 2026
Thank you for providing a repro! We'll take a look at this issue soon.
lodev09 commentedon Oct 30, 2026
I'm closing this as intended behavior and it is consistent on all platforms. The views are resized natively and JS gets the update normally, I don't see anything wrong with it since this is a supported behavior with yoga.
react-native-screensdoes this as well on their formSheets.