Ran into this while building an outliner app with drag 'n' drop. I added custom two finger interactions to make it possible to drag an item while scrolling and opening/closing trees. The item would constantly be moved in the items array to create an empty spot where it would drop. Opening and closing trees underneath the dragged/hovering item would mess up the alignment of items that moved underneath it. Also confirmed using a dev build on my physical iPhone 16 Pro btw, so it's not only on the simulator. Didn't test Android though.
--- Below is output from Claude Opus 5 ---
AnimatedLegendList with itemLayoutAnimation and getFixedItemSize settles some rows off their correct position. The offset does not recover — it stays until the list remounts.
Repro (single self-contained component): https://gist.github.com/m9tdev/e990ed5a450810346e73378cd1b02d67
Setup
| package |
version |
@legendapp/list |
3.3.3 |
react-native-reanimated |
4.5.0 |
react-native |
0.86.0 |
react |
19.2.3 |
expo |
57.0.8 |
iPhone 17 Pro simulator, iOS 26.5, macOS 26.3.1. Debug and release builds both show it.
Steps
- Render the component from the gist.
- Press Run 16 fast toggles (90ms apart).
- Do not scroll. Read the guide lines.
Each toggle does two things at once:
- expands or collapses a parent, so the row list is re-derived and every row arrives as a fresh object;
- moves one extra
>>> floating <<< row to a new index.
The second part is what matters. Rows must reorder around the floating row, not merely shift down as a block. Toggling alone does not trigger the bug — I could not reproduce it until I added the moving row.
Toggles are 90ms apart and the transition is 160ms, so each transition is interrupted mid-flight.
Expected
Every row sits on a guide line. Rows are a fixed 30pt and getFixedItemSize returns 30, so every gap must be 30pt.
Actual
At least one gap comes out ~3pt wrong, and every row below inherits the shift and keeps it. Measured through the accessibility tree (normalized screen fractions, 30pt = 0.0343):
child 1 0.261
>>> floating <<< 0.298 Δ 0.0372 ← 3.5pt long
child 2 0.330 Δ 0.0320 ← 3pt short
child 3 0.364 Δ 0.0343 ok
...
child 13 0.707 Δ 0.0343 ok
parent 1 0.738 Δ 0.0310 ← 3pt short
parent 2 0.772 Δ 0.0343 ok
Nothing re-settles it. Further toggles keep the shift, and can add more. Only remounting the list clears it. Which rows land wrong varies per run; every run so far has had at least one.
Controls
| change |
result |
LAYOUT_ANIMATION = false |
aligned, all gaps 0.0343 |
RECYCLE_ITEMS = false |
still drifts — not a factor |
The control run is the same 16 toggles with the same data churn, only without itemLayoutAnimation. It ends perfectly uniform end to end.
Why it matters
This came out of a drag-and-drop outliner. A dragged row is spliced into the list at a moving index while the user opens and closes subtrees underneath it — exactly the pattern above. After a few seconds of dragging the list is visibly crooked and stays that way. I had to drop itemLayoutAnimation entirely.
Guess, not a diagnosis
Legend List positions each row through a React top style applied at commit time. itemLayoutAnimation positions it through a Reanimated animated style applied on the UI thread. The two can never change on the same frame. When a row's top changes while its layout transition is still in flight, the two sources appear to disagree about where the row started, and the difference is kept.
That is inferred from behaviour only — I have not read the internals. Treat it as a hint, not a diagnosis.
Ran into this while building an outliner app with drag 'n' drop. I added custom two finger interactions to make it possible to drag an item while scrolling and opening/closing trees. The item would constantly be moved in the items array to create an empty spot where it would drop. Opening and closing trees underneath the dragged/hovering item would mess up the alignment of items that moved underneath it. Also confirmed using a dev build on my physical iPhone 16 Pro btw, so it's not only on the simulator. Didn't test Android though.
--- Below is output from Claude Opus 5 ---
AnimatedLegendListwithitemLayoutAnimationandgetFixedItemSizesettles some rows off their correct position. The offset does not recover — it stays until the list remounts.Repro (single self-contained component): https://gist.github.com/m9tdev/e990ed5a450810346e73378cd1b02d67
Setup
@legendapp/listreact-native-reanimatedreact-nativereactexpoiPhone 17 Pro simulator, iOS 26.5, macOS 26.3.1. Debug and release builds both show it.
Steps
Each toggle does two things at once:
>>> floating <<<row to a new index.The second part is what matters. Rows must reorder around the floating row, not merely shift down as a block. Toggling alone does not trigger the bug — I could not reproduce it until I added the moving row.
Toggles are 90ms apart and the transition is 160ms, so each transition is interrupted mid-flight.
Expected
Every row sits on a guide line. Rows are a fixed 30pt and
getFixedItemSizereturns 30, so every gap must be 30pt.Actual
At least one gap comes out ~3pt wrong, and every row below inherits the shift and keeps it. Measured through the accessibility tree (normalized screen fractions, 30pt = 0.0343):
Nothing re-settles it. Further toggles keep the shift, and can add more. Only remounting the list clears it. Which rows land wrong varies per run; every run so far has had at least one.
Controls
LAYOUT_ANIMATION = falseRECYCLE_ITEMS = falseThe control run is the same 16 toggles with the same data churn, only without
itemLayoutAnimation. It ends perfectly uniform end to end.Why it matters
This came out of a drag-and-drop outliner. A dragged row is spliced into the list at a moving index while the user opens and closes subtrees underneath it — exactly the pattern above. After a few seconds of dragging the list is visibly crooked and stays that way. I had to drop
itemLayoutAnimationentirely.Guess, not a diagnosis
Legend List positions each row through a React
topstyle applied at commit time.itemLayoutAnimationpositions it through a Reanimated animated style applied on the UI thread. The two can never change on the same frame. When a row'stopchanges while its layout transition is still in flight, the two sources appear to disagree about where the row started, and the difference is kept.That is inferred from behaviour only — I have not read the internals. Treat it as a hint, not a diagnosis.