Let's build a slider from scratch in React Native
Tags: reactnative, react, javascript, slider
Published: 2020-12-06 21:20:05

The other day I implemented a component based on a design which was about a unique vertical slider for selecting the user's height. I started to look around for already existing solutions but nothing seemed to fit, so I created one from scratch. Hope it helps someone out there.

![Slider design](https://dev-to-uploads.s3.amazonaws.com/i/d7hmunltrjzg82br3bfj.png)

# TLDR

I created a repo in case you just want to spin up a test project and try things out.
[Check it here](https://github.com/baloghroli/react-native-slider)

# Starting point

Firstly, you need to create a React Native project. I won't go into details as there are plenty of nice articles in the topic.
[Here is a link about setting up all the things](https://reactnative.dev/docs/environment-setup)

We will build the slider in the `App.js` file.

# The building blocks

After starting a React Native project we can finally code.
Let's add the basics of our slider. The code below contains the parts of it with some basic styles. I'll run through these in a bit.

```javascript
import React from 'react';
import {SafeAreaView, StyleSheet, Text, View} from 'react-native';

const App = () => {
  return (
    <SafeAreaView>
      <Text style={styles.title}>What's your height?</Text>
      <View style={styles.slider}>
        <View style={styles.rail}>
          <View style={styles.railFill} />
        </View>
        <View style={styles.stepper} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  title: {
    textAlign: 'center',
    fontSize: 20,
    marginVertical: 50,
  },
  slider: {
    width: 50,
    height: '80%',
    marginLeft: 'auto',
    marginRight: 'auto',
    position: 'relative',
    marginBottom: 50,
  },
  rail: {
    width: 20,
    height: '100%',
    marginLeft: 'auto',
    marginRight: 'auto',
    backgroundColor: '#DBDBDB',
  },
  stepper: {
    width: '100%',
    height: 5,
    backgroundColor: 'black',
  },
});

export default App;
```

## Slider

This `View` contains all of our slider content. I set the height to `80%` instead of using pixels to prevent accidents in smaller phones.

## Rail

The rail is the place where the stepper will actually slide up and down. It contains a `View` which is going to fill the rail indicating where we are in the slider.

## Stepper

Grabbing the stepper we can actually move it to set a value for the slider and also indicating the status.

At this point we have a not too good looking slider which doesn't respond to anything:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9r5e3rzpiwi8p7nfgmm7.png)

# Functionality

Let's make this slider respond to the outside world. For this we can use React Native's `PanResponder` and `Animated` API. These two APIs can work together really nicely, so let's use them.

Firstly we need to calculate our slider's dimensions.
We can use the state for this:

```javascript
const [sliderDimensions, setSliderDimensions] = useState({
  height: null,
  top: null,
  bottom: null,
});
```

We'll store the slider's `height`, `top` and `bottom` values here. The `top` and `bottom` values are the boundaries of the slider on the screen.

To get these values we need to use the `onLayout` function on the slider `View`. With this we can get the component's dimensions when mount or layout change occurs:

```javascript
<View
  style={styles.slider}
  onLayout={(evt) => {
    const {height, y} = evt.nativeEvent.layout;
    setSliderDimensions({
      height: height,
      top: y,
      bottom: y + height,
    });
  }}>
  <View style={styles.rail}>
    <View style={styles.railFill} />
  </View>
  <View style={styles.stepper} />
</View>
```

After we have the dimensions we create two `Animated` instances. One for the stepper and one for the rail fill.

```javascript
const stepperAnim = useRef(new Animated.Value(0)).current;
const railFillAnim = useRef(new Animated.Value(0)).current;
```

```javascript
<View
  style={styles.slider}
  onLayout={(evt) => {
    const {height, y} = evt.nativeEvent.layout;
    setSliderDimensions({
      height,
      top: y,
      bottom: y + height,
    });
  }}>
  <View style={styles.rail}>
    <Animated.View style={styles.railFill} />
  </View>
  <Animated.View style={styles.stepper} />
</View>
```

Last part for the functionality is the `PanResponder`. We can create a responder with the `create` method. Here we need to implement four callbacks.

```javascript
const stepperResponder = PanResponder.create({
  onStartShouldSetPanResponder: () => true,
  onPanResponderGrant: () => {
    stepperAnim.setOffset(stepperAnim._value);
    railFillAnim.setOffset(railFillAnim._value);
  },
  onPanResponderMove: (evt, {dy, moveY}) => {
    if (moveY > sliderDimensions.top && moveY < sliderDimensions.bottom) {
      stepperAnim.setValue(dy);
      railFillAnim.setValue(-dy);
    }
  },
  onPanResponderRelease: () => {
    stepperAnim.flattenOffset();
    railFillAnim.flattenOffset();
  },
});
```

## onStartShouldSetPanResponder

With returning `true` the `PanResponder` will become active when there is a gesture on the component.

## onPanResponderGrant

This callback fires when the initial gesture occurs which will be the press on the stepper. Here we need to make sure to set the current position of the stepper and the fill as offset. This is important because we want to start the next gesture from the position we stopped before.
We can achieve this with the `setOffset` method of the `Animated` API.

## onPanResponderMove

Here we need to handle the dragging gesture itself. In the callback we get the event, and the `gestureState` objects and the second one provides us useful informations.

The `dy` field gives us the accumulated distance since the gesture started which means it starts from zero every time. We need to set the stepper animation value to this. If we have an offset on the stepper animation, this value will be added to that.

The `moveY` field gives us the vertical location of the movement on the screen. With this, and the already calculated slider dimensions we can create a condition to make the stepper move only between the slider boundaries.

## onPanResponderRelease

When the user releases the stepper we need to reset the offset to zero with the `flattenOffset` method.

We need to attach the responder to the stepper view and use the animation value to move the stepper along the Y axis. Here is our stepper after the changes:

```javascript
<Animated.View
  {...stepperResponder.panHandlers}
  style={[
    styles.stepper,
    {
      transform: [{translateY: stepperAnim}],
    },
  ]}
/>
```

Finally we need to create the fill color:

```javascript
// Rail View
<Animated.View style={[styles.railFill, {height: railFillAnim}]} />;

// Rail style
const styles = StyleSheet.create({
  //...
  railFill: {
    width: '100%',
    backgroundColor: '#CBAA71',
    position: 'absolute',
    bottom: 0,
  },
});
```

# Making things prettier

We still need to implement the stripes for our slider somehow.
For this we can fill the rail with extra `View`s which have a white color, same as the whole app. This will finally give us the colored stripes.

The code below maps through an array and renders a `View` with the height of five pixels, and a spacing of another five pixels by setting the bottom position for each.

```javascript
<Animated.View style={[styles.railFill, {height: railFillAnim}]}>
  {sliderDimensions.height
    ? Array.apply(
        null,
        Array(Math.floor(sliderDimensions.height / 10)),
      ).map((item, index) => (
        <View
          key={index}
          style={[styles.railFillSpace, {bottom: index * 10}]}
        />
      ))
    : null}
</Animated.View>
```

You can see the final code [here](https://github.com/baloghroli/react-native-slider/blob/master/App.js).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4tf9c4j2xfse4olmalop.gif)

This is it, the last stripe part went a little bit hacky but for now I'm happy it works.

On the other hand I hope this little article encourages you to start experimenting without using third parties. It is a good way to learn new things in a library or framework.

If you have any feedback, or opinion what you would do differently let me know in the comments below.