Skip to content

Item disappears during rapid drags: hoverReadySV/draggedIdSV stale from runOnUI worklet + measurement race on shiftsRef #250

@timgent

Description

@timgent

Environment

  • react-native-drax: 1.1.0
  • react-native-reanimated: 4.x
  • Platform: web and native

Symptom

When dragging list items rapidly (releasing one drag and starting another quickly), an item disappears from the list. The visual glitch is permanent until the next data refresh.

Root cause — two related races

Race 1: stale hoverReadySV / draggedIdSV between rapid drags

In finalizeDrag (SortableContainer.tsx), the drag state is cleared via a runOnUI worklet:

runOnUI((_shifts) => {
  'worklet';
  // ...
  hoverReadySV.value = false;   // ← cleared asynchronously
  draggedIdSV.value = '';       // ← cleared asynchronously
  // ...
})(finalShifts);

requestAnimationFrame(() => {
  onReorder(reorderEvent);
  // ...
});

The runOnUI worklet is dispatched before the RAF but executes asynchronously on the UI thread. If the user starts a second drag before the worklet completes:

  • hoverReadySV.value is still true
  • draggedIdSV.value still holds the previous item's DraxView ID
  • SortableItem.useAnimatedStyle evaluates isDragged = hoverReadySV.value && draggedIdSV.value === viewIdSV.value as true for the previously-dragged item
  • That item gets opacity: 0 — it vanishes

The codebase already acknowledges this issue for draggedItem (onMonitorDragStart comment: "Reanimated 4 doesn't reliably sync SharedValue writes from runOnUI worklets") but the same race applies to hoverReadySV and draggedIdSV.

Race 2: stale measurement recorded due to concurrent shiftsRef read/write

On web, onMeasure fires on every layout change caused by Reanimated transforms. The sequence:

  1. Drag ends → runOnUI dispatched to set shiftsRef.value = finalShifts
  2. RAF → onReorder → consumer calls setState → React re-renders → DOM transforms change → onLayoutonMeasure
  3. onMeasure reads shiftsRef.value[itemKey] (line 238) to subtract the shift from the raw measurement
  4. Race: if the runOnUI worklet hasn't completed, shiftsRef.value may be the intermediate/wrong value
  5. Measurement stored with incorrect baseline
  6. Next drag's computeShiftsForOrder uses wrong measurement → computes wrong target offset → item positioned off-screen

Suggested fixes

For Race 1: Reset hoverReadySV and draggedIdSV on the JS thread (direct assignment) before dispatching the runOnUI block, rather than relying on the worklet to clear them. Or reset them in onMonitorDragStart alongside the existing draggedItem handling.

For Race 2: Use a plain JS ref mirror for shifts (safe to read from onMeasure without Reanimated warnings or race conditions). See also issue #249.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions