React Native Reanimated DnDReact Native Reanimated DnD
API ReferenceComponents

Draggable Component

A versatile draggable component with advanced features like collision detection, bounded dragging, axis constraints, and custom animations.

Overview

The Draggable component provides a complete drag-and-drop solution that can be used standalone or within a DropProvider context for drop zone interactions. It supports both full-item dragging and handle-based dragging patterns.

Import

import { Draggable } from "react-native-reanimated-dnd";

Props

Core Props

data

  • Type: TData
  • Required: Yes
  • Description: Data payload associated with this draggable item. This data is passed to drop handlers when the item is successfully dropped.
const taskData = { id: "1", title: "Complete project", priority: "high" };

<Draggable data={taskData}>
  <Text>Drag me!</Text>
</Draggable>;

children

  • Type: React.ReactNode
  • Required: Yes
  • Description: The content to render inside the draggable component.

style

  • Type: StyleProp<ViewStyle>
  • Required: No
  • Description: Style to apply to the draggable container.
<Draggable
  data={data}
  style={[styles.draggableItem, { backgroundColor: "#f0f0f0" }]}
>
  <Text>Styled draggable</Text>
</Draggable>

Interaction Props

dragDisabled

  • Type: boolean
  • Default: false
  • Description: Whether dragging is disabled for this item. When true, the item cannot be dragged.
<Draggable data={data} dragDisabled={!user.canDrag}>
  <Text>Conditionally draggable</Text>
</Draggable>

draggableId

  • Type: string
  • Required: No
  • Description: Unique identifier for this draggable item. If not provided, one will be generated automatically.

Callback Props

onDragStart

  • Type: (data: TData) => void
  • Required: No
  • Description: Callback fired when dragging starts.
<Draggable
  data={data}
  onDragStart={(data) => {
    console.log("Started dragging:", data.title);
    hapticFeedback();
  }}
>
  <Text>Drag me!</Text>
</Draggable>

onDragEnd

  • Type: (data: TData) => void
  • Required: No
  • Description: Callback fired when dragging ends (regardless of whether it was dropped successfully).
<Draggable
  data={data}
  onDragEnd={(data) => {
    console.log("Finished dragging:", data.title);
    setIsDragging(false);
  }}
>
  <Text>Drag me!</Text>
</Draggable>

onDragging

  • Type: (payload: DraggingPayload<TData>) => void
  • Required: No
  • Description: Callback fired continuously while dragging. Useful for real-time feedback.
<Draggable
  data={data}
  onDragging={({ x, y, tx, ty, itemData }) => {
    const currentX = x + tx;
    const currentY = y + ty;
    console.log(`${itemData.title} is at (${currentX}, ${currentY})`);
  }}
>
  <Text>Drag me!</Text>
</Draggable>

onStateChange

  • Type: (state: DraggableState) => void
  • Required: No
  • Description: Callback fired when the draggable state changes.
<Draggable
  data={data}
  onStateChange={(state) => {
    if (state === DraggableState.DROPPED) {
      showSuccessMessage();
    }
  }}
>
  <Text>Drag me!</Text>
</Draggable>

Advanced Props

animationFunction

  • Type: AnimationFunction
  • Required: No
  • Description: Custom animation function for controlling how the item animates when dropped. If not provided, uses default spring animation.
const bounceAnimation = (toValue) => {
  "worklet";
  return withTiming(toValue, {
    duration: 600,
    easing: Easing.bounce,
  });
};

<Draggable data={data} animationFunction={bounceAnimation}>
  <Text>Bouncy draggable</Text>
</Draggable>;

dragBoundsRef

  • Type: React.RefObject<Animated.View | View>
  • Required: No
  • Description: Reference to a View that defines the dragging boundaries. The draggable item will be constrained within this view's bounds.
const boundsRef = useRef<View>(null);

return (
  <View ref={boundsRef} style={styles.container}>
    <Draggable data={data} dragBoundsRef={boundsRef}>
      <Text>Bounded draggable</Text>
    </Draggable>
  </View>
);

dragAxis

  • Type: "x" | "y" | "both"
  • Default: "both"
  • Description: Constrains dragging to a specific axis.
// Horizontal slider
<Draggable
  data={data}
  dragAxis="x"
>
  <Text>Horizontal only</Text>
</Draggable>

// Vertical slider
<Draggable
  data={data}
  dragAxis="y"
>
  <Text>Vertical only</Text>
</Draggable>

collisionAlgorithm

  • Type: CollisionAlgorithm
  • Default: "intersect"
  • Description: Algorithm used for collision detection with drop zones.

Available algorithms:

  • intersect: Collision when any part overlaps (default)
  • center: Collision when center point is over droppable
  • contain: Collision when entire draggable is contained
<Draggable data={data} collisionAlgorithm="center">
  <Text>Precise dropping</Text>
</Draggable>

Draggable.Handle

A handle component that can be used within Draggable to create a specific draggable area. When a Handle is present, only the handle area can initiate dragging.

Props

children

  • Type: React.ReactNode
  • Required: Yes
  • Description: The content to render inside the handle.

style

  • Type: StyleProp<ViewStyle>
  • Required: No
  • Description: Optional style to apply to the handle.

Handle Examples

// Basic drag handle
<Draggable data={data}>
  <View style={styles.itemContent}>
    <Text>Item content (not draggable)</Text>

    <Draggable.Handle style={styles.dragHandle}>
      <Icon name="drag-handle" size={20} />
    </Draggable.Handle>
  </View>
</Draggable>

// Custom styled handle
<Draggable data={data}>
  <View style={styles.card}>
    <Text style={styles.title}>Card Title</Text>
    <Text style={styles.content}>Card content...</Text>

    <Draggable.Handle style={styles.customHandle}>
      <View style={styles.handleDots}>
        <View style={styles.dot} />
        <View style={styles.dot} />
        <View style={styles.dot} />
      </View>
    </Draggable.Handle>
  </View>
</Draggable>

Usage Examples

Basic Draggable

import { Draggable, DraggableState } from "react-native-reanimated-dnd";

function TaskItem({ task }) {
  return (
    <Draggable
      data={task}
      onDragStart={(data) => console.log("Started dragging:", data.title)}
      onDragEnd={(data) => console.log("Finished dragging:", data.title)}
    >
      <View style={styles.taskItem}>
        <Text>{task.title}</Text>
        <Text>Priority: {task.priority}</Text>
      </View>
    </Draggable>
  );
}

Draggable with Handle

function TaskCard({ task }) {
  return (
    <Draggable data={task}>
      <View style={styles.card}>
        <Draggable.Handle style={styles.handle}>
          <Icon name="drag-handle" size={20} />
        </Draggable.Handle>

        <View style={styles.content}>
          <Text>{task.title}</Text>
          <Text>{task.description}</Text>
        </View>
      </View>
    </Draggable>
  );
}

Bounded Draggable

function BoundedDraggable() {
  const boundsRef = useRef<View>(null);

  return (
    <View ref={boundsRef} style={styles.container}>
      <Draggable
        data={{ id: "1", name: "Bounded Item" }}
        dragBoundsRef={boundsRef}
        dragAxis="x"
      >
        <View style={styles.slider}>
          <Text>Drag me horizontally</Text>
        </View>
      </Draggable>
    </View>
  );
}

State-Aware Draggable

function StatefulDraggable({ task }) {
  const [dragState, setDragState] = useState(DraggableState.IDLE);

  return (
    <Draggable
      data={task}
      onStateChange={setDragState}
      style={[
        styles.item,
        dragState === DraggableState.DRAGGING && styles.dragging,
        dragState === DraggableState.DROPPED && styles.dropped,
      ]}
    >
      <View style={styles.content}>
        <Text>{task.title}</Text>
        <Text>State: {dragState}</Text>
      </View>
    </Draggable>
  );
}

Custom Animation

import { withSpring, withTiming, Easing } from "react-native-reanimated";

const customBounce = (toValue) => {
  "worklet";
  return withSpring(toValue, {
    damping: 10,
    stiffness: 100,
    mass: 1,
  });
};

function AnimatedDraggable() {
  return (
    <Draggable data={{ id: "1" }} animationFunction={customBounce}>
      <Text>Bouncy draggable</Text>
    </Draggable>
  );
}

File Drag and Drop

import { GestureHandlerRootView } from "react-native-gesture-handler";

function FileDragDrop() {
  return (
    <GestureHandlerRootView style={styles.container}>
      <DropProvider>
        <View style={styles.content}>
          <Draggable
            data={{ id: "1", type: "file", name: "document.pdf" }}
            collisionAlgorithm="intersect"
            onDragStart={(data) => {
              hapticFeedback();
              setDraggedFile(data);
            }}
            onDragEnd={(data) => {
              setDraggedFile(null);
            }}
          >
            <View style={styles.fileItem}>
              <Icon name="file-pdf" size={24} />
              <Text>{data.name}</Text>
            </View>
          </Draggable>

          <Droppable
            onDrop={(data) => {
              console.log("File dropped:", data.name);
              moveToTrash(data.id);
            }}
          >
            <View style={styles.trashZone}>
              <Icon name="trash" size={32} />
              <Text>Drop files here to delete</Text>
            </View>
          </Droppable>
        </View>
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Conditional Dragging

function ConditionalDraggable({ item, canDrag }) {
  return (
    <Draggable
      data={item}
      dragDisabled={!canDrag}
      onDragStart={(data) => {
        if (data.locked) {
          showError("This item is locked");
          return;
        }
        analytics.track("drag_start", { itemId: data.id });
      }}
      style={[styles.item, !canDrag && styles.disabled]}
    >
      <View style={styles.itemContent}>
        <Text style={styles.itemTitle}>{item.title}</Text>
        {item.locked && <Icon name="lock" size={16} />}
        {!canDrag && <Text style={styles.disabledText}>Drag disabled</Text>}
      </View>
    </Draggable>
  );
}

TypeScript Support

The component is fully typed with generic support:

interface TaskData {
  id: string;
  title: string;
  priority: "low" | "medium" | "high";
}

function TypedDraggable() {
  return (
    <Draggable<TaskData>
      data={{ id: "1", title: "Task 1", priority: "high" }}
      onDragStart={(data: TaskData) => {
        // data is properly typed
        console.log(`Started dragging: ${data.title}`);
      }}
    >
      <Text>Typed draggable</Text>
    </Draggable>
  );
}

Performance Tips

  1. Use useCallback for event handlers to prevent unnecessary re-renders
  2. Memoize expensive calculations with useMemo
  3. Use handles for large draggable components to improve performance
  4. Throttle position updates in onDragging callbacks

Accessibility

  • The component automatically handles accessibility for drag operations
  • Provide meaningful content for screen readers
  • Consider alternative interaction methods for users with disabilities

See Also