Fix race condition causing items to disappear after drag direction reversal#619
Open
chetstone wants to merge 1 commit intocomputerjazz:mainfrom
Open
Fix race condition causing items to disappear after drag direction reversal#619chetstone wants to merge 1 commit intocomputerjazz:mainfrom
chetstone wants to merge 1 commit intocomputerjazz:mainfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a race condition where dragged items disappear and blank placeholder boxes appear when the user overshoots a drag target and reverses direction. The visual state is corrupted but the data is correct — any action that forces a re-render restores the list.
Relates to #350, #500.
This only reproduces on fast physical devices (iPad Pro M5, iPadOS 26.2), not on simulators or slower hardware, consistent with a timing-sensitive race.
Demo Images
The list before dragging:

The list after dragging "Counter 1" in between 189 and 190, then reversing direction and dropping it between 193 and 194. Note the placeholder box stuck between 189 and 190, and the "Counter 1" item has disappeared.

The list after tapping "Done", which exits draggable mode and rerenders the list with Counter 1 appearing between 193 and 194 as intended

Root Cause
Three interrelated timing issues in the drag-end lifecycle:
1. Distributed
spacerIndexAnimwrites inuseCellTranslate(primary)Each cell's
useDerivedValueworklet independently checks hover overlap and writes tospacerIndexAnim.value. During a direction reversal near the active cell's origin, both a before-active and an after-active cell can satisfy their overlap conditions in the same frame. The last worklet to execute wins nondeterministically, producing an incorrect spacer index that cascades into wrongplaceholderOffsetand wrong spring animation target.2. Stale
heldTranslateinCellRendererComponent(secondary)heldTranslatepreserves the last animated translation whenactiveKeygoes null (to prevent flicker during reorder). It only resets to 0 inonCellLayout. If FlatList doesn't detect a geometry change for the affected cell,onCellLayoutnever fires, leaving the cell stuck at its wrong position.3.
dataHasChangedreset races with spring callback (tertiary)When the consumer updates the
dataprop inonDragEnd, thedataHasChangedcheck firesInteractionManager.runAfterInteractions(() => reset()), which can clearspacerIndexAnimwhile the spring animation callback is still reading it.Changes
useCellTranslate.tsx: Add a direction-aware guard tospacerIndexAnimwrites. Only allow a cell to claim the spacer index if it's on the correct side relative to the hover direction (hovering below origin → only after-active cells can write; hovering above → only before-active). This eliminates the frame-order race during reversals.CellRendererComponent.tsx: ResetheldTranslateviauseEffectwhenactiveKeytransitions to null. By this point the spring animation has completed, so this is safe and ensures cells don't stay at stale positions whenonCellLayoutdoesn't fire.DraggableFlatList.tsx: Guard thedataHasChangedreset with!activeKeysoreset()doesn't race with the spring animation callback during an active drag.Testing
Tested on:
Note: A pre-existing issue on older Android devices where autoscroll occasionally stops during long drags is not affected by this change (reproduces on the unpatched version as well).
🤖 Generated with Claude Code