The drag and drop component we will build

Create a Drag and Drop Component in React Native

Build Draggable Elements Using the PanResponder API

Bi Yoo
React Native Coach
Published in
4 min readNov 15, 2017

--

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.

What we will be build

Our finished component

Creating a Draggable 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:

Looks good!

Returning the Circle to its Initial Location

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:

Smooth animation is one of the benefits of using RN

Creating the Drop Area

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:

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.

Conclusion

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.

--

--