Skip to main content

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