Summary
On some Android devices (confirmed on a Xiaomi 11T / MIUI; not reproducible on a Samsung Galaxy Z Flip 7), adding or removing an item from Sortable.Grid's data array (not via drag — via a normal state update) causes:
- A visible render glitch for a frame or two around the resize (items appear cut/overlapping), and
- Any sibling rendered after the
Sortable.Grid in the same flex column keeps a stale touch/hit area — it visually looks fine, but stops responding to taps — until something forces a full native re-layout (e.g. unmounting and remounting the screen).
Both symptoms trace back to the same place: SortableContainer's own container resize doesn't go through a normal Yoga-triggering commit.
Environment
react-native-sortables: 1.10.0
react-native-reanimated: 4.5.1
react-native-worklets: 0.10.1
react-native: 0.86.2 (New Architecture / Fabric, always-on at this RN version)
- Expo SDK 57
- Repro device: Xiaomi 11T (MIUI). Not reproducible on a Samsung Galaxy Z Flip 7 with the same build.
Our setup
<Sortable.Grid
columns={1}
customHandle
hapticsEnabled
data={entries} // heterogeneous: mixes two item "kinds" in one sequence
keyExtractor={(entry) => entry.id}
renderItem={...}
rowGap={spacing.sm}
scrollableRef={scrollableRef}
sortEnabled={canEdit}
onDragEnd={({ data }) => ...}
/>
entries is derived from two live DB queries (useLiveQuery) merged and sorted — a normal place/checklist gets added or removed via a separate "Add place" button below the grid, not by dragging.
Root cause (as far as we can tell from the source)
In SortableContainer.tsx:
const outerContainerStyle = useAnimatedStyle(() => {
...
return {
height: maybeAnimate(
controlledContainerDimensions.height ? containerHeight.value : null,
animateWorklet // only true when dimensionsAnimationType === 'worklet'
),
...
};
});
<Animated.View
layout={animateLayout ? LinearTransition : undefined} // only when dimensionsAnimationType === 'layout'
style={[outerContainerStyle, ...]}
>
With dimensionsAnimationType left at its default ('none'), maybeAnimate returns the raw containerHeight.value and it's applied straight through useAnimatedStyle on the UI thread. That updates the container's own painted box correctly, but (as far as we can tell) never goes through the JS-thread Yoga diff that layout-driven LinearTransition normally participates in — so anything positioned after the grid in a parent flex column never gets repositioned/re-measured for hit-testing when the grid's content-driven height changes. The paint looks right; the touch area doesn't move.
What we tried
- Ancestor
layout={LinearTransition} wrapping the whole Sortable.Grid region (not inside SortableContainer — one level up, in our own tree): fixes the stale-touch-area problem reliably, at every duration we tried, including LinearTransition.duration(0). But on the Xiaomi 11T, having any animated layout transition there (regardless of duration) reintroduces the visible render glitch. Duration doesn't seem to be the variable — the mere presence of an active Reanimated layout-animation commit at that ancestor is what MIUI's compositor seems to glitch on.
- Removing that ancestor's
layout prop entirely: fixes the visible glitch on MIUI, but reintroduces the stale-touch-area bug (confirming it's the same underlying issue, not two independent bugs).
dimensionsAnimationType="layout" on Sortable.Grid itself (letting the library apply its own layout={LinearTransition} on SortableContainer's wrapper, per the source above): did not fix the stale-touch-area problem. Our guess is that because the height write on that exact node comes from useAnimatedStyle (UI thread) rather than a normal style/props diff, Reanimated's layout-animation system never detects a "layout changed" event to animate in the first place, so the layout prop is effectively a no-op there.
- Remounting
Sortable.Grid itself via a key tied to data.length: also did not fix the stale-touch-area problem, ruling out "a fresh mount would just measure correctly" — the same UI-thread-only height write applies to a freshly mounted instance too.
The only thing that ever fixed the stale-touch-area problem was a layout transition living outside SortableContainer, in code we control — which is exactly what breaks on MIUI. We don't have a fix that satisfies both at once; we ended up keeping the ancestor LinearTransition (functional correctness) and accepting the cosmetic MIUI glitch as the lesser problem for now.
Reproduction
We don't have an isolated minimal repro package (this surfaced inside a larger app), but the shape is straightforward to reproduce with a Sortable.Grid of variable-height items where an item is added/removed by mutating data from outside the grid (not by dragging), with something interactive rendered as a sibling after the grid in the same flex container. Happy to help narrow this down further if useful, or to test a patch on the reporting device (Xiaomi 11T) if one is available.
Summary
On some Android devices (confirmed on a Xiaomi 11T / MIUI; not reproducible on a Samsung Galaxy Z Flip 7), adding or removing an item from
Sortable.Grid'sdataarray (not via drag — via a normal state update) causes:Sortable.Gridin the same flex column keeps a stale touch/hit area — it visually looks fine, but stops responding to taps — until something forces a full native re-layout (e.g. unmounting and remounting the screen).Both symptoms trace back to the same place:
SortableContainer's own container resize doesn't go through a normal Yoga-triggering commit.Environment
react-native-sortables: 1.10.0react-native-reanimated: 4.5.1react-native-worklets: 0.10.1react-native: 0.86.2 (New Architecture / Fabric, always-on at this RN version)Our setup
entriesis derived from two live DB queries (useLiveQuery) merged and sorted — a normal place/checklist gets added or removed via a separate "Add place" button below the grid, not by dragging.Root cause (as far as we can tell from the source)
In
SortableContainer.tsx:With
dimensionsAnimationTypeleft at its default ('none'),maybeAnimatereturns the rawcontainerHeight.valueand it's applied straight throughuseAnimatedStyleon the UI thread. That updates the container's own painted box correctly, but (as far as we can tell) never goes through the JS-thread Yoga diff thatlayout-drivenLinearTransitionnormally participates in — so anything positioned after the grid in a parent flex column never gets repositioned/re-measured for hit-testing when the grid's content-driven height changes. The paint looks right; the touch area doesn't move.What we tried
layout={LinearTransition}wrapping the wholeSortable.Gridregion (not insideSortableContainer— one level up, in our own tree): fixes the stale-touch-area problem reliably, at every duration we tried, includingLinearTransition.duration(0). But on the Xiaomi 11T, having any animatedlayouttransition there (regardless of duration) reintroduces the visible render glitch. Duration doesn't seem to be the variable — the mere presence of an active Reanimated layout-animation commit at that ancestor is what MIUI's compositor seems to glitch on.layoutprop entirely: fixes the visible glitch on MIUI, but reintroduces the stale-touch-area bug (confirming it's the same underlying issue, not two independent bugs).dimensionsAnimationType="layout"onSortable.Griditself (letting the library apply its ownlayout={LinearTransition}onSortableContainer's wrapper, per the source above): did not fix the stale-touch-area problem. Our guess is that because the height write on that exact node comes fromuseAnimatedStyle(UI thread) rather than a normal style/props diff, Reanimated's layout-animation system never detects a "layout changed" event to animate in the first place, so thelayoutprop is effectively a no-op there.Sortable.Griditself via akeytied todata.length: also did not fix the stale-touch-area problem, ruling out "a fresh mount would just measure correctly" — the same UI-thread-only height write applies to a freshly mounted instance too.The only thing that ever fixed the stale-touch-area problem was a
layouttransition living outsideSortableContainer, in code we control — which is exactly what breaks on MIUI. We don't have a fix that satisfies both at once; we ended up keeping the ancestorLinearTransition(functional correctness) and accepting the cosmetic MIUI glitch as the lesser problem for now.Reproduction
We don't have an isolated minimal repro package (this surfaced inside a larger app), but the shape is straightforward to reproduce with a
Sortable.Gridof variable-height items where an item is added/removed by mutatingdatafrom outside the grid (not by dragging), with something interactive rendered as a sibling after the grid in the same flex container. Happy to help narrow this down further if useful, or to test a patch on the reporting device (Xiaomi 11T) if one is available.