react-native-reanimated-dnd

repository·main·Indexed 21 days ago

https://github.com/entropyconquers/react-native-reanimated-dnd

A high-performance drag-and-drop library for React Native built on Reanimated 4 and Worklets to provide 60fps animations. It features core components like Draggable, Droppable, and Sortable for implementing reorderable vertical lists, grids, and basic drag-and-drop interactions. The library supports axis and boundary constraints, a 9-point alignment system, and custom animation functions, requiring the React Native New Architecture.

Tokens
175.3K
Snippets
427
Records
554
Agent score
73%

What's inside react-native-reanimated-dnd

  1. Features of react-native-reanimated-dnd

    main

    The library provides a comprehensive suite of drag-and-drop capabilities:

    • Core Components: Draggable, Droppable, and Sortable components.
    • Layout Patterns:
      • Vertical & Horizontal Sortable Lists: Reorderable lists with automatic scrolling.
      • Sortable Grids: 2D grid reordering with insert and swap modes.
      • Dynamic Heights: Support for sortable lists with variable item heights.
    • Interaction Controls:
      • Drag Handles: Dedicated regions (bars, headers, or full items) for precise control.
      • Collision Detection: Algorithms including center, intersect, and contain.
      • Boundary Constraints: Keep draggables within specific areas or lock them to a specific axis.
      • Custom Animations: Support for spring, timing, or custom animation functions.
    • Advanced Capabilities:
      • FlatList Performance: Optional virtualization for large datasets.
      • State Lifecycle: Tracking via a State enum and onStateChange callbacks (States: Idle, Dragging, Dropped).
      • Alignment: 9-point alignment system with custom X/Y offsets.
  2. What is DropProvider and how does it work?

    main

    The DropProvider is the foundational context provider required to enable drag-and-drop functionality in your application. It acts as the central orchestrator for the library by managing several core responsibilities:

    • Drop Zone Registration: Keeps track of all registered Droppable areas.
    • Collision Detection: Calculates when a Draggable item intersects with a Droppable zone.
    • State Management: Maintains the mapping of which items have been dropped into which zones.
    • Position Updates: Handles layout changes and recalculates positions to ensure accuracy.
    • Capacity Management: Enforces limits on how many items a drop zone can hold.
    • Global Callbacks: Provides hooks for application-wide drag event handling (start, end, dragging, etc.).

    All Draggable and Droppable components must be descendants of a DropProvider to communicate with each other.

    <DropProvider>
      <Draggable ... />
      <Droppable ... />
    </DropProvider>
  3. What is DragDropContext (SlotsContext)?

    main

    The DragDropContext (exported as SlotsContext) is the central React context that provides the infrastructure for drag-and-drop functionality in react-native-reanimated-dnd. It is automatically created by the DropProvider and serves as the communication layer between draggable and droppable components.

    It manages several core responsibilities:

    • Drop Zone Registration: Tracking all registered droppable areas.
    • Active State Management: Monitoring which drop zones are currently hovered/active.
    • Position Updates: Handling layout changes and recalculating positions.
    • Dropped Items Tracking: Maintaining state of which items are dropped where.
    • Capacity Management: Enforcing drop zone capacity limits.
    • Event Coordination: Coordinating drag events across the component tree.
  4. Handle sortable callbacks: onMove, onDragStart, onDrop, and onDragging

    main

    The hook provides several lifecycle callbacks for managing state and side effects during the drag-and-drop process:

    • onMove(id, from, to): Triggered when an item's index changes. Use this to update your underlying data model (e.g., reordering an array).
    • onDragStart(id, position): Triggered when a drag begins. Useful for haptic feedback or updating global UI states like isDragging.
    • onDrop(id, position): Triggered when the drag ends. Use this to finalize data changes or trigger success notifications.
    • onDragging(id, overItemId, xPosition): Triggered continuously during the drag. Use this for real-time feedback like hover states or scroll hints based on xPosition relative to the containerWidth.
  5. Best practices for data structures in react-native-reanimated-dnd

    main

    When using react-native-reanimated-dnd, ensure your data follows these rules to prevent reordering bugs:

    • Use string IDs: All items must have an id field that is a string. Numeric IDs are not supported.
    • Use stable, unique IDs: Do not use array indices as IDs, as this will break the reordering logic. Use unique identifiers like UUIDs.
    • Avoid missing IDs: Every object in your data array must contain an id field.
    // GOOD — simple, flat data with string IDs
    const items = [
      { id: '1', title: 'Item 1' },
      { id: '2', title: 'Item 2' },
    ];
    
    // GOOD — use stable unique IDs (not array indices)
    const items = tasks.map(task => ({ ...task, id: task.uuid }));
    
    // BAD — numeric IDs (must be strings)
    const items = [{ id: 1, title: 'Item 1' }];
    
    // BAD — using array index as ID (breaks on reorder)
    const items = data.map((d, i) => ({ ...d, id: String(i) }));
    
    // BAD — missing ID field
    const items = [{ title: 'Item 1' }];
  6. Understand DraggableState transitions

    main

    The useDraggable hook tracks the lifecycle of a drag interaction via the DraggableState enum:

    • IDLE: The item is at its rest position.
    • DRAGGING: The user is actively moving the item.
    • DROPPED: The item was successfully dropped on a valid droppable.

    Transitions:

    • IDLEDRAGGING: User starts dragging.
    • DRAGGINGDROPPED: Successful drop on a droppable.
    • DRAGGINGIDLE: Drag ends without a successful drop.
    • DROPPEDIDLE: Animation completes and item returns to position.
  7. Performance considerations

    main

    To maintain high performance and avoid jank:

    • Sortable Remounting: Note that the Sortable component remounts the entire list whenever the data array changes (using a hash of IDs as the key). This resets scroll position and animation state. For more granular control, use the hooks API (useSortableList + useSortable) directly.
    • Lightweight Handlers: Keep onDragging handlers lightweight. They fire approximately 20 times per second and bridge from the UI thread to JS. Heavy computation here will cause frame drops.
    • Grid Efficiency: Unlike Sortable, SortableGrid does not remount the entire list on data changes, making it more efficient for frequent updates.
  8. Handle Draggable States

    main

    The Draggable component tracks its lifecycle through three distinct states, which can be monitored via the onStateChange callback:

    • IDLE: The component is at its rest position.
    • DRAGGING: The component is being actively moved by the user.
    • DROPPED: The component has been successfully dropped.

    You can use these states to apply conditional styling or trigger animations.

    enum DraggableState {
      IDLE = "IDLE",
      DRAGGING = "DRAGGING",
      DROPPED = "DROPPED",
    }
  9. Manage state correctly with SortableItem

    main

    SortableItem and its parent Sortable component maintain internal state for high performance. Do not update external state directly in sortable callbacks (like onMove), as this will break the internal state management and cause issues with reordering.

    Correct Usage

    • Use onMove only for side effects like logging or analytics.
    • Use onDrop with the allPositions argument for read-only position tracking.
    • Let the sortable system handle the actual reordering of items automatically.

    Incorrect Usage

    • Never call reorderTasks(), setItems(), or similar inside onMove.
    • Never update arrays, Redux stores, or Zustand stores directly from drag events.
    • Never manually splice or modify external arrays during drag operations.
  10. Related hooks and components for sortable lists

    main

    Depending on your layout requirements, you may want to use these related abstractions instead of or alongside useHorizontalSortable:

    • useHorizontalSortableList: Use this for managing the state and logic of an entire horizontal sortable list.
    • useSortable: Use this for vertical sortable items.
    • SortableItem: A high-level component that simplifies the implementation of sortable items.
    • Horizontal Sortable Example: A complete implementation reference for horizontal layouts.
  11. How content width is calculated in useHorizontalSortableList

    main

    The hook automatically calculates the contentWidth used for the contentContainerStyle of the ScrollView. The formula used is:

    contentWidth = (itemsCount * itemWidth) + ((itemsCount - 1) * gap) + (paddingHorizontal * 2)

    Example Calculation:

    • 5 items × 120px width = 600px
    • 4 gaps × 10px gap = 40px
    • 2 × 16px padding = 32px
    • Total: 672px
  12. Configure collision algorithms in useDraggable

    main

    You can control how a draggable item detects collisions with droppables using the collisionAlgorithm option. This is useful for different UX requirements:

    • intersect (Default): Collision is detected when any part of the draggable overlaps a droppable. Best for general drag-and-drop.
    • center: Collision is detected only when the center point of the draggable is over a droppable. Best for precise placement or grid layouts.
    • contain: Collision is detected only when the entire draggable item is within the boundaries of a droppable. Best for strict containment like folder systems.
    const { animatedViewProps, gesture } = useDraggable({
      data: { id: "3", name: "Collision Test" },
      collisionAlgorithm: "center", // Options: "intersect", "center", "contain"
    });