React Native Reanimated DnDReact Native Reanimated DnD
Getting Started

Setup Provider

Learn how to configure the DropProvider for your application.

Basic Setup

The DropProvider is the foundation of the drag-and-drop system. It must wrap all draggable and droppable components to enable communication between them.

import React from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider } from "react-native-reanimated-dnd";

function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider>
        {/* Your draggable and droppable components */}
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Provider Props

The DropProvider accepts several optional props for customizing behavior and receiving callbacks:

interface DropProviderProps {
  children: ReactNode;
  onLayoutUpdateComplete?: () => void;
  onDroppedItemsUpdate?: (droppedItems: DroppedItemsMap) => void;
  onDragging?: (payload: DraggingPayload) => void;
  onDragStart?: (data: any) => void;
  onDragEnd?: (data: any) => void;
}

Basic Provider with Callbacks

import React from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider } from "react-native-reanimated-dnd";

function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        onDragStart={(data) => console.log("Drag started:", data)}
        onDragEnd={(data) => console.log("Drag ended:", data)}
        onDroppedItemsUpdate={(items) => console.log("Items updated:", items)}
      >
        <YourComponents />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Advanced Provider Configuration

import React, { useState } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider } from "react-native-reanimated-dnd";

function AdvancedApp() {
  const [droppedItems, setDroppedItems] = useState({});
  const [isDragging, setIsDragging] = useState(false);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        onDragStart={(data) => {
          setIsDragging(true);
          console.log("Started dragging:", data);
        }}
        onDragEnd={(data) => {
          setIsDragging(false);
          console.log("Finished dragging:", data);
        }}
        onDragging={({ x, y, tx, ty, itemData }) => {
          console.log(`Dragging ${itemData.name} at (${x + tx}, ${y + ty})`);
        }}
        onDroppedItemsUpdate={(items) => {
          setDroppedItems(items);
          console.log("Dropped items updated:", items);
        }}
        onLayoutUpdateComplete={() => {
          console.log("Layout update complete");
        }}
      >
        <YourComponents isDragging={isDragging} droppedItems={droppedItems} />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Provider with Ref

You can use a ref to access provider methods imperatively:

import React, { useRef } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider, DropProviderRef } from "react-native-reanimated-dnd";

function AppWithRef() {
  const dropProviderRef = useRef<DropProviderRef>(null);

  const handleLayoutChange = () => {
    // Manually trigger position updates after layout changes
    dropProviderRef.current?.requestPositionUpdate();
  };

  const getDroppedItems = () => {
    const items = dropProviderRef.current?.getDroppedItems();
    console.log("Current dropped items:", items);
  };

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        ref={dropProviderRef}
        onLayoutUpdateComplete={() => console.log("Layout updated")}
      >
        <ScrollView onLayout={handleLayoutChange}>
          <YourComponents />
        </ScrollView>
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Real-World Examples

Task Management App

import React, { useState, useCallback } from "react";
import { View, ScrollView } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider } from "react-native-reanimated-dnd";

interface Task {
  id: string;
  title: string;
  status: "todo" | "in-progress" | "done";
  priority: "low" | "medium" | "high";
}

function TaskManagementApp() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [draggedTask, setDraggedTask] = useState<Task | null>(null);

  const handleDragStart = useCallback((data: Task) => {
    setDraggedTask(data);
    // Add visual feedback
    hapticFeedback();
  }, []);

  const handleDragEnd = useCallback((data: Task) => {
    setDraggedTask(null);
    // Clean up visual feedback
  }, []);

  const handleDroppedItemsUpdate = useCallback(
    (droppedItems) => {
      // Sync dropped items with your state management
      syncTasksWithDroppedItems(droppedItems);

      // Save to backend
      saveTasks(tasks);

      // Analytics
      analytics.track("tasks_reordered", {
        totalTasks: tasks.length,
        droppedCount: Object.keys(droppedItems).length,
      });
    },
    [tasks]
  );

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
        onDroppedItemsUpdate={handleDroppedItemsUpdate}
        onDragging={({ itemData, tx, ty }) => {
          // Real-time position tracking
          updateDragPreview(itemData, tx, ty);
        }}
      >
        <View style={{ flex: 1 }}>
          <TaskBoard tasks={tasks} draggedTask={draggedTask} />
        </View>
      </DropProvider>
    </GestureHandlerRootView>
  );
}

File Manager with Drag & Drop

import React, { useState, useRef } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider, DropProviderRef } from "react-native-reanimated-dnd";

interface FileItem {
  id: string;
  name: string;
  type: "file" | "folder";
  size: number;
}

function FileManager() {
  const [files, setFiles] = useState<FileItem[]>([]);
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
  const dropProviderRef = useRef<DropProviderRef>(null);

  const handleFileOperation = useCallback(
    (operation: string, fileId: string) => {
      // Trigger position update after file operations
      setTimeout(() => {
        dropProviderRef.current?.requestPositionUpdate();
      }, 100);
    },
    []
  );

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        ref={dropProviderRef}
        onDragStart={(file: FileItem) => {
          console.log(`Started dragging: ${file.name}`);
          setSelectedFiles(new Set([file.id]));
        }}
        onDragEnd={(file: FileItem) => {
          console.log(`Finished dragging: ${file.name}`);
        }}
        onDroppedItemsUpdate={(droppedItems) => {
          // Update file organization
          updateFileStructure(droppedItems);
        }}
      >
        <FileExplorer
          files={files}
          selectedFiles={selectedFiles}
          onFileOperation={handleFileOperation}
        />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

E-commerce Product Catalog

import React, { useState } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { DropProvider } from "react-native-reanimated-dnd";

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
}

function ProductCatalog() {
  const [cart, setCart] = useState<Product[]>([]);
  const [wishlist, setWishlist] = useState<Product[]>([]);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider
        onDragStart={(product: Product) => {
          analytics.track("product_drag_start", {
            productId: product.id,
            category: product.category,
          });
        }}
        onDroppedItemsUpdate={(droppedItems) => {
          // Update cart and wishlist based on drops
          updateProductLists(droppedItems);
        }}
        onDragging={({ itemData, tx, ty }) => {
          // Show drag preview
          showProductPreview(itemData, tx, ty);
        }}
      >
        <ProductGrid />
        <ShoppingCart items={cart} />
        <Wishlist items={wishlist} />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Provider Ref Methods

The DropProviderRef interface provides imperative methods:

interface DropProviderRef {
  requestPositionUpdate: () => void;
  getDroppedItems: () => DroppedItemsMap;
}

Using Provider Methods

function ComponentWithProviderAccess() {
  const providerRef = useRef<DropProviderRef>(null);

  const refreshPositions = () => {
    // Call this after layout changes, orientation changes, etc.
    providerRef.current?.requestPositionUpdate();
  };

  const logDroppedItems = () => {
    const items = providerRef.current?.getDroppedItems();
    console.log("Currently dropped items:", items);
  };

  const handleOrientationChange = () => {
    // Refresh positions after orientation change
    setTimeout(refreshPositions, 100);
  };

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider ref={providerRef}>
        <OrientationListener onOrientationChange={handleOrientationChange} />
        <YourComponents />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

Best Practices

1. Single Provider per App

Use one DropProvider at the root of your drag-and-drop area:

// Good: Single provider at the root
<GestureHandlerRootView style={{ flex: 1 }}>
  <DropProvider>
    <App />
  </DropProvider>
</GestureHandlerRootView>

// Bad: Multiple nested providers
<GestureHandlerRootView style={{ flex: 1 }}>
  <DropProvider>
    <Screen1 />
    <DropProvider> {/* Unnecessary nesting */}
      <Screen2 />
    </DropProvider>
  </DropProvider>
</GestureHandlerRootView>

2. Handle Layout Changes

Always trigger position updates after layout changes:

<GestureHandlerRootView style={{ flex: 1 }}>
  <DropProvider
    ref={providerRef}
    onLayoutUpdateComplete={() => {
      // Layout update completed
      console.log("Positions updated");
    }}
  >
    <ScrollView
      onLayout={() => {
        // Trigger update after scroll view layout
        providerRef.current?.requestPositionUpdate();
      }}
    >
      <YourComponents />
    </ScrollView>
  </DropProvider>
</GestureHandlerRootView>

3. Optimize Callbacks

Use useCallback to prevent unnecessary re-renders:

const handleDragStart = useCallback((data) => {
  console.log("Drag started:", data);
}, []);

const handleDroppedItemsUpdate = useCallback((items) => {
  updateState(items);
}, []);

<GestureHandlerRootView style={{ flex: 1 }}>
  <DropProvider
    onDragStart={handleDragStart}
    onDroppedItemsUpdate={handleDroppedItemsUpdate}
  >
    <YourComponents />
  </DropProvider>
</GestureHandlerRootView>;

Common Patterns

State Synchronization

function StateSyncExample() {
  const [appState, setAppState] = useState(initialState);

  const syncWithDroppedItems = useCallback((droppedItems) => {
    // Convert dropped items to your app state format
    const newState = convertDroppedItemsToState(droppedItems);
    setAppState(newState);

    // Persist to storage
    AsyncStorage.setItem("appState", JSON.stringify(newState));
  }, []);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider onDroppedItemsUpdate={syncWithDroppedItems}>
        <YourApp state={appState} />
      </DropProvider>
    </GestureHandlerRootView>
  );
}

See Also