Image for post
Image for post
The drag and drop component we will build

Create a Drag and Drop Component in React Native

Build Draggable Elements Using the PanResponder API

While brainstorming my next React Native app, here’s my first, I wanted to see if there’s an API that tracks gesture so I could create a drag and drop component.

Luckily, it wasn’t too hard to find: we have the PanResponder! With this we can track gesture as an abstracted state and update our UI accordingly.

Image for post
Our finished component

I found the documentation for PanResponder API is a bit hard to follow, but this example and animation guide helped get me going

After importing PanResponder class, you initiate it with .create method in componentWillMount() life cycle method. You could also write it in the constructor() .

import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated
} from "react-native";

export default class Draggable extends Component {
constructor() {
super();
this.state = {
pan: new Animated.ValueXY()
};
}

componentWillMount() {
// Add a listener for the delta value change
this._val = { x:0, y:0 }
this.state.pan.addListener((value) => this._val = value);
// Initialize PanResponder with move handling
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (e, gesture) => true,
onPanResponderMove: Animated.event([
null, { dx: this.state.pan.x, dy: this.state.pan.y }
])
// adjusting delta value
this.state.pan.setValue({ x:0, y:0})
});
}

render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
}
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[panStyle, styles.circle]}
/>
);
}
}

let CIRCLE_RADIUS = 30;
let styles = StyleSheet.create({
circle: {
backgroundColor: "skyblue",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS
}
});

this.panResponder = PanResponder.create() initiates the panResponder and creates a reference. We utilize it in our <Animate.View> component by passing{…this.panResponder.panHandlers} like we’re passing a group of props.

Inside thePanResponder.create() we set onStartShouldSetPanResponder to true so the panResponder will respond to touch feedback. Then we pass an Animated.event to onPanResponderMove: to update the location of our Animated.View circle component that the user is gesturing with.

To get our circle’s position we get the calculated animated value from this.state.pan.getTranslateTransform() and use it to create a transform style that we pass to our Animated.View.

Finally, we adjust delta value so the element won’t jump on the second touch.

At this point we have a draggable circle that the user can interact with:

Image for post
Looks good!

We want the circle to return to its original location when they release the component. To do this we use onPanResponderRelease to tell the UI how to respond when the user lets go.

componentWillMount() {
...
this.panResponder = PanResponder.create({
...
onPanResponderRelease: (e, gesture) => {
Animated.spring(this.state.pan, {
toValue: { x: 0, y: 0 },
friction: 5
}).start();
}
});

Our circle now returns to its initial location:

Image for post
Smooth animation is one of the benefits of using RN

Now that we have the drag, we need the drop. We create another component with our drop area and use our Draggable component within it:

import React, { Component } from "react";
import { StyleSheet, View, Text } from "react-native";
import Draggable from "./Draggable";

export default class Screen extends Component {
render() {
return (
<View style={styles.mainContainer}>
<View style={styles.dropZone}>
<Text style={styles.text}>Drop them here!</Text>
</View>
<View style={styles.ballContainer} />
<View style={styles.row}>
<Draggable />
<Draggable />
<Draggable />
<Draggable />
<Draggable />
</View>
</View>
);
}
}

const styles = StyleSheet.create({
mainContainer: {
flex: 1
},
ballContainer: {
height:200
},
row: {
flexDirection: "row"
},
dropZone: {
height: 200,
backgroundColor: "#00334d"
},
text: {
marginTop: 25,
marginLeft: 5,
marginRight: 5,
textAlign: "center",
color: "#fff",
fontSize: 25,
fontWeight: "bold"
}
});

Then we add a bit of logic to Draggable, its onPanResponderRelease handler, and add a function to determine if we’re in the drop area. Note: We make assumptions about the drop zone’s location on the screen here.

...
constructor()
super.props();
this.state = {
showDraggable: true,
dropAreaValues: null,
pan: new Animated.ValueXY(),
opacity: new Animated.Value(1)
};
}
componentWillMount() {
...
this.panResponder = PanResponder.create({
...
onPanResponderRelease: (e, gesture) => {
if (this.isDropArea(gesture)) {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() =>
this.setState({
showDraggable: false
})
);
} else {
Animated.spring(this.state.pan, {
toValue: { x: 0, y: 0 },
friction: 5
}).start();
}
}
isDropArea(gesture) {
return gesture.moveY < 200;
}

And that’s it. Our Screen should now look like this:

Image for post
Our completed drag and drop component

To see the source code and play with the live demo check out the Expo Snack I created for this demo.

The PanResponder API was a bit intimidating for me at first, but now I think this is a great API that anyone can use to make the user experience in their apps better.

I hope this article has helped you out, reach out in the comments with questions or concerns. Thanks for reading, and happy coding!

*Korean version of this article hasn’t written yet.

React Native Coach

Bringing you the best React Native, GraphQL, Redux, and…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store