Building a Movable Animated Component in React Native
Tags: reactnative, react, animated, panresponder
Published: 2020-10-23 21:43:36

---
title: Building a Movable Animated Component in React Native
published: true
description: Tutorial to build a movable component in React Native using the Animated and PanResponder APIs
tags: #reactnative, #react, #animated #panresponder
cover_image: https://www.appstud.com/wp-content/uploads/2018/03/React-Native-Titre.png
---
While building out the front end of a mobile application for a large client, my team and I found ourselves in need of a React Native input component similar to an HTML range-type input. The desired functionality was for a user to pick a single value from 0-10 along a range input by either “sliding” the input cursor or pressing on a section of the component. We started calling this hypothetical component a “slider” or a “slider picker”, so that’s the language I’ll be using from here on out in regards to it.

We struggled to find an npm package for this functionality that got along with our existing stack and the version of Expo we were forced to use at the time, so I was tasked with building out the component. I spent a couple of days building out a rudimentary first version that was *not great*. It didn’t really slide so much as it allowed presses within the dimensions of the component, but we needed to get the app’s MVP out the door so this was our reality for now. 

Eventually, there was time for me to refactor the existing component. Using the React Native [Animated](https://reactnative.dev/docs/animated) and [PanResponder](https://reactnative.dev/docs/panresponder) APIs, I was able to make a proper slider component that I was proud of. 

In this first of two articles I’ll walk you through the process of building a moveable React Native component that the user can “drag” around the screen, and in doing so we’ll develop a foundational understanding of the Animated and PanResponder React Native APIs. In a subsequent article, I'll be walking through the steps to convert a simple moveable component into an actual slider input.

**A bare-bones look at what we’ll build**

![Finished product of component](https://res.cloudinary.com/munsch-creative/image/upload/v1603488044/movable-dot_pfs0vk.gif)

**Getting started**

When I’m prototyping for React Native, I like to do it in a sample repo with as little overhead as possible; I recommend doing the same before integrating the slider into any project.

The only dependency necessary for building out our slider is [react-native-css-vh-vw](https://github.com/graftonstudio/react-native-css-vh-vw#readme), a ditto of the vh and vw units in CSS for React Native. This package itself has no dependencies and at the time of writing this is under 4kb.

Let’s start with installing our sole dependency. At the root of your project, run the following in your terminal:

`npm install react-native-css-vh-vw`

Now let’s go ahead and start with some boilerplate in `Movable.js`.

```javascript
import React, { Component } from "react";
import { Animated, PanResponder, StyleSheet } from "react-native";
import { vh, vw } from 'react-native-css-vh-vw';
 
export class Movable extends Component {
 constructor(props) {
   super(props);
 
   // Initialize state
   this.state = {
     // Create instance of Animated.XY, which interpolates
     // X and Y values
     animate: new Animated.ValueXY() // Inits both x and y to 0
   };
 
   // Set value of x and y coordinate
   this.state.animate.setValue({ x: 0, y: 0 });
 
   [...] 
 }
```

Here in `Movable.js`, we’ve got our boilerplate along with the first bit of logic we need for getting going with Animated and PanResponder.

While we’re at it, let’s make a component that will act as the container/screen for `Movable.js`:

```javascript
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { vh, vw } from 'react-native-css-vh-vw';

import { Movable } from '../components/Generics/Movable';
 
export class Container extends Component {
 render() {
   return (
     <View style={styles.container}>
       <Movable />
     </View>
   );
 }
}
 
const styles = StyleSheet.create({
 container: {
   height: vh(100),
   backgroundColor: '#fff',
   alignItems: 'center',
   justifyContent: 'center',
 }
});
```



First, we have some configuring to do with the [Animated API](https://reactnative.dev/docs/animated) - in the component’s constructor we initialize `this.state.animate`, setting it to **new** `Animated.ValueXY()` *(docs* [*here*](https://reactnative.dev/docs/animatedvaluexy)*)* and immediately call [setValue()](https://reactnative.dev/docs/0.5/animatedvalue#setvalue) on it. When this eventually gets passed to our instance of `<Animated.View>` it sets the x/y position of the component when it is rendered, relative to any styling you may place on it through the style prop. For example, I could execute `this.state.animate.setValue({ x: 200, y: -150 });` and when the component first renders, it will be positioned 200 pixels to the right and 150 above the center of the screen (because the parent container is styled to position content vertically and horizontally).

*Note: In addition to React Native’s official docs, I also found* [*this*](https://animationbook.codedaily.io/animated-value-xy) *page from* [*https://animationbook.codedaily.io/*](https://animationbook.codedaily.io/) *helpful in understanding `Animated.ValueXY()`.*

```javascript
 [...]
 
   // Initialize panResponder and configure handlers
   this._panResponder = PanResponder.create({
     //     
     // Asks to be the touch responder for a
     // press on the View
     //
     onMoveShouldSetPanResponder: () => true,
     //
     // Actions taken when the View has begun
     // responding to touch events
     //
     onPanResponderGrant: () => {
       //
       // Set offset state.animate to prevent
       // Animated.View from returning to 0      
       // coordinates when it is moved again.
       //
       this.state.animate.setOffset({
         x: this.state.animate.x._value,
         y: this.state.animate.y._value
       });
       //
       // Set value to 0/0 to prevent AnimatedView
       // from "jumping" on start of
       // animate. Stabilizes the component.
       //
       this.state.animate.setValue({x: 0, y: 0})
     },
     //
     // The user is moving their finger
     //
     onPanResponderMove: (e, gesture) => {
       //
       // Set value of state.animate x/y to the
       // delta value of each
       //
       this.state.animate.setValue({
         x: gesture.dx,
         y: gesture.dy
       });
     },
     //
     // Fired at the end of the touch
     //
     onPanResponderRelease: () => {
       //
       // Merges the offset value into the
       // base value and resets the offset
       // to zero
       //
       this.state.animate.flattenOffset();
     }
   });
} // End of constructor
 
  render() {
   return (
     <Animated.View
       // Pass all panHandlers to our AnimatedView
       {...this._panResponder.panHandlers}
       //
       // getLayout() converts {x, y} into 
       // {left, top} for use in style
       //
       style={[
         this.state.animate.getLayout(),
         styles.button
       ]}
     />
   )
 }
```



Copy and paste the contents of the above code block into `Movable.js`and open up the iOS simulator on your machine (for now let’s ignore the actual contents of the code and make sure it’s working as expected). Your output should look very close to the gif above and have the same functionality.

With the simple movable component working, let’s take a further look into the code that provides us with the desired functionality.

For now, disregard any of the code that is within the `render()` function; we’ll get to that in a bit.

Divert your attention to the code we’ve added to the component’s `constructor()`. We’ve created an instance of PanResponder and configured its handlers. The [documentation](https://reactnative.dev/docs/panresponder) for the API is a bit confusing in my opinion *(the* [*Usage Pattern*](https://reactnative.dev/docs/panresponder#usage-pattern) *section is helpful)*, but the main takeaway is that it converts a series of touches into a single gesture, so that “drag” of a finger is really a series of several touch events that are consolidated into one. To achieve this functionality, the API uses a tweaked version of the [Gesture Responder System](https://reactnative.dev/docs/gesture-responder-system). 

To use PanResponder, you’ll need to [create](https://reactnative.dev/docs/panresponder#create) an instance and pass an object of key/value pair handlers. Reference the code block above to see the behavior and usage of each handler needed for a simple draggable component. We’ll be passing an arrow function to each handler to set the desired behavior. Let’s walk through that configuration object, one handler at a time.

*Note: We won’t be using every PanResponder handler available.*

The first key we’ll pass to `PanResponder.create()` is `onMoveShouldSetPanResponder`, which simply returns `true`. You can think of this as telling the View with the handler that it can “claim” the touch event taking place.

Second is `onPanResponderGrant`: this is where any logic will take place that we want executed when the `View` has started responding to touch events. Here, we need to call two functions for our instance of `AnimatedValue` stored in `this.state.animate - setOffset()` and `setValue()`.

According to the React Native docs, `setOffset()`:

“Sets an offset that is applied on top of whatever value is set, whether via `setValue`, an animation, or `Animated.event`. Useful for compensating things like the start of a pan gesture.” In the context of our example, we pass an object with a key/value pair for both x and y to `setOffset()`, each value the respective one of `this.state.animate` at the time when the touch event has begun.

```javascript
this.state.animate.setOffset({
  x: this.state.animate.x._value,
  y: this.state.animate.y._value
});
```

Now when the touch event begins, an update to the value of `this.state.animate` accounts for the value of its most recent `x` and `y` offsets; without this the values would update in relation to the values for `x` and `y` that you set when you first called `this.state.animate.setValue()` in the constructor (in our case `x: 0`,` y: 0`). To see what this logic really offers our little app, let’s remove it. Comment out this line of code, refresh your Simulator, and try moving around the component again and see how behavior changes.

![Component with incorrect setOffset() logic](https://res.cloudinary.com/munsch-creative/image/upload/v1603488624/setOffset-tinkering_xhpp81.gif)

Still within `onPanResponderGrant`, we execute `this.state.animate.setValue()` once again passing an object with a key/value pair for both `x` and `y`, this time with them both set to zero. Let’s take a quick look at what the React Native docs have to say about this method: “Directly set the value. This will stop any animations running on the value and update all the bound properties.” To use layman’s terms, this prevents the possibility of our animated component from “jumping” around the container at the beginning of a touch event. At this point, calling `setValue()` here is more of a precautionary measure, but it will have clearer implications when we start converting this component into an actual Slider. Like with `setOffset()`, let’s tweak this line of code to get a better sense of what it does: change the `x` value to `1000` and the `y` value to `50`, refresh your Simulator, and try moving the component around again.

![Component with incorrect setOffset() logic](https://res.cloudinary.com/munsch-creative/image/upload/v1603488904/setValue-tinkering_a7bvbj.gif)

The next PanResponder handler is `onPanResponderMove`, the handler for when the user moves their finger during the touch event. For this handler, we’ll be using one of the two parameters that can be passed to each PanResponder handler, `gestureState`. The other parameter is `nativeEvent`, which we must still pass even though we won’t be using it (see the PanResponder docs for a detailed look at each parameter). We’re going to take two values within `gestureState` and pass them to `this.state.animate.setValue()`, `gestureState.dx` and `gestureState.dy`. 

```javascript
// The user is moving their finger
onPanResponderMove: (e, gesture) => {
  // Set value of state.animate x/y to the delta value of each
  this.state.animate.setValue({ x: gesture.dx, y: gesture.dy });
},
```



These two values are relatively straightforward; according to the [docs](https://reactnative.dev/docs/panresponder), they represent “accumulated distance of the gesture since the touch started” for each respective axis. Let’s say you move the component 50 pixels to the left, `gesture.dx` will have a value of `-50`. If you move the component 125 pixels towards the bottom of the device’s screen, `gesture.dy` will have a value of `125`. Gestures of the same distance in the opposite direction would be `50` and `125` respectively.

The final PanResponder handler used is `onPanResponderRelease`, which is fired at the end of the touch when the user lifts their finger. In cases where you’d like to set a local state value or execute a callback to hoist state to a parent container, this is likely the best handler to do it in. For now, we simply call `flattenOffset()` on `this.state.animate` - according to the [docs](https://reactnative.dev/docs/animatedvaluexy#flattenoffset), this “merges the offset value into the base value and resets the offset to zero.”[1] This merge happens without changing the output value of the animation event. If we remove `flattenOffset()` the first one or two drags of our component look ok, but continued drags will show the problem with not resetting the offset value.

![No flattenOffset() example](https://res.cloudinary.com/munsch-creative/image/upload/v1603489069/flattenOffset_-tinkering_enu5yl.gif)

With our PanResponder handlers configured, let’s turn our attention to what’s going on inside of our `render()` function.

Instead of using a normal `View` component, we use the `Animated.View` component and pass all the PanResponder handlers as props using an object with the JavaScript spread operator. Finally, we pass an array to the component’s style prop with whatever styles you’d like to apply to your component (I’ve included mine below), along with a call to `this.state.animate.getLayout()` - which converts the `Animated` instance’s `x` and `y` values to left and top style values respectively.

```javascript
const styles = StyleSheet.create({
  button: {
    width: vw(6),
    height: vw(6),
    borderRadius: vw(100), * .5,
    borderWidth: 1,
    backgroundColor: 'blue'
  }
});
```



Now we have a fully moveable React Native component! It’s not much at this point, but feel free to continue along in the following article to finish building out our Slider component.

_Thanks for reading! The second article is coming very soon!_