react-native-nested-drag v0.7.1
Overview
This library is a declarative drag-and-drop system for React Native, written in TypeScript. It is designed to be flexible and powerful with the goal of supporting many use cases. Default settings should cover the most common scenarios with minimal configuration.
Screenshots

More screenshots
Snack
There is an example on snack though it works fine only on real device.
Installation
npm i react-native-nested-dragor using yarn
yarn add react-native-nested-dragUsage
Import the library components:
import { DragView, DropView, DragProvider } from 'react-native-nested-drag'Basic example
<DragProvider>
<DropView>
<Text>drop here!</Text>
</DropView>
<DragView>
<Text>drag me</Text>
</DragView>
</DragProvider>DragProvider
Wrap the part of your application that you want to enable drag and drop for inside DragProvider.
<DragProvider>
{/* */}
</DragProvider>Props
All props are optional.
| Prop | Type | Default | Description |
| ------------- | ---------- | ------------- | ------------- |
| mockEventManager | IDndEventManager | | Only for testing |
| overlapMode | OverlapMode | 'last' | Specifies events call order when multiple DropView in same area. After sorting with comparer, DropView events will be called last to first. You can't change it dynamically.|
DragView
The element that you want to make draggable.
The DragView won't be actually dragged, instead a copy appears on DragStart to make sure element always on top.
<DragView payload={1}>
<View style={styles.itemContainer}>
<Text>+1</Text>
</View>
</DragView>Props
All props are optional.
Available all ViewProps except for GestureResponderHandlers
| Prop | Type | Default | Description |
|---|---|---|---|
payload | any | An arbitrary value (often) unique to this draggable that can later be used to determine which draggable item was dropped onto a droppable | |
style | ViewStyle | Style applied while this view is NOT being dragged | |
dragStyle | ViewStyle | Style applied while this view is being dragged | |
overStyle | ViewStyle | Style applied while this view is being dragged over a receiver | |
copyDragStyle | ViewStyle | Style applied to the copy while this view is being dragged | |
copyOverStyle | ViewStyle | Style applied to the copy while this view is being dragged over a receiver | |
disabled | boolean | false | Specifies whether this view can be dragged |
longPressDelay | number | 0 | Time interval in ms after which this view can be dragged |
vibroDuration | number | 0 | Vibration duration on drag start in ms |
movable | boolean | false | Specifies whether this view stays where it has been dragged |
movableOffset | IPosition | {x:0,y:0} | It useful only when you want nested movable DragView appear correctly inside the draggable copy or for restoring movable state. This prop shouldn't be used just for formatting |
onDragStart | DraggableDragStart | Callback that is triggerd when user starts dragging the DragView | |
onDrag | DraggableDragStart | Callback that is triggered repeatedly when user dragging the DragView not over any receiver | |
onEnter | DraggableEnterOver | Callback that is triggered when the DragView initially dragged over a new receiver | |
onOver | DraggableEnterOver | Callback that is triggered repeatedly when the DragView dragged over a receiver | |
onExit | DraggableExit | Callback that is triggered when the DragView leaves the droppable area | |
onDragEnd | DraggableEnd | Callback that is triggered when the DragView drag ends not over any receiver or is cancelled | |
onDrop | DraggableDrop | Callback that is triggered when the DragView drag ends over a receiver | |
animationEndOptions | SpringAnimationConfig | { overshootClamping: true} | Animated.SpringAnimationConfig to customize dragEnd animation |
animationDropOptions | TimingAnimationConfig | Animated.TimingAnimationConfig to customize drop fadeout animation |
DragHandleView
DragHandleView is useful when you want to restrict area where drag can start.
Just place one or more inside DragView.
Notice that Button and TouchableOpacity work inside DragView and also restrict possible drag start area (you can't start drag on buttons).
<DragView>
<Text>part 1</Text>
<DragHandleView style={styles.handle}>
<Text>drag here!</Text>
</DragHandleView>
</DragView>Props
All props are optional.
Available all ViewProps except for GestureResponderHandlers
DropView
The element that you want to receive droppable events.
const drop = (_, payload) => {
console.log(payload)
}
//...
<DropView onDrop={add}>
<Text>drop here!</Text>
</DropView>Props
All props are optional.
Available all ViewProps
| Prop | Type | Default | Description |
|---|---|---|---|
payload | any | An arbitrary value (often) unique to this DropView that can later be used to determine in which DropView the DragView was dropped to | |
style | ViewStyle | Style applied while over this DropView is NOT dragging a DragView | |
overStyle | ViewStyle | Style applied while dragging a DragView over this DropView | |
disabled | boolean | false | Specifies whether this view receives DnD events |
onEnter | DroppableEnterExit | Callback that is triggered when a DragView initially dragged over this DropView | |
onOver | DroppableDropOver | Callback that is triggered repeatedly when a DragView dragged over this DropView | |
onExit | DroppableEnterExit | Callback that is triggered when a DragView leaves this DropView | |
onDrop | DroppableDropOver | Callback that is triggered when a DragView dragging ends over this DropView |
Testing
Import the library components:
import { DragProvider, MockDndEventManager } from 'react-native-nested-drag'Create mockEventManager:
const mockEventManager = new MockDndEventManager()Wrap your component for test render:
<DragProvider mockEventManager={mockEventManager}>
<YourComponent>
</DragProvider>Call DnD action like so:
mockEventManager.drop(mockEventManager.draggables[0], mockEventManager.droppables[0])If you have animation errors set before the action:
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')
jest.useFakeTimers()Full example with 'testing-library/react-native'
License
This software library is licensed under the MIT License.
Caveats and tips
If nested elements rendered base on own state clone appears as initial state. Possible workaround is move that state outside
DragProvider(e.g. state management library).If you want to store offset for
movableDragView(may be because previous point)onDragEndandonDropreceives it as parameter.Don't put these styles on
DragView(if necessary addViewinside or outside):margintransformpositionopasity(allowed fordragStyleandoverStyleonly)
%
widthstyle doesn't work well in many cases.There is
useEffectlisteners on props so make sure your props won't change on every render.
instead
...movableOffset={{x:50, y:-10}}...use
...
const [movableOffset] = useState({x:50, y:-10});
...movableOffset={movableOffset}...Available option for overlap handling is
next callback(foronDropandonOver)Overlapping
DropViewis tricky especially when nested.
graph LR;
A-->B;
A-->C;
D;where B and C nested to A.
Droppables order: B, C, A, D
Moreover if you are using dynamic 'disabled' prop, which causes unregister order may become completely unpredictable.
You may try to use next callback or comparer function for overlapMode but much easier just avoid overlapping whenever it's possible.
onLayoutevent probably still calls only once (when component renders first time) insideDragViewandDropView(also don't use it inside 'View' withonLayoutif elements may change positions on screen besides dragging.)