Simple Slide Menu with React Native

React Native is amazing. I have been using it for some time and I am preparing our first production application implemented entirely with it.

The only drawback that you might have experienced currently is that there are not that many components to work with.

Therefore you have two options if you need a particular kind of component. You can either take an existing Objective-C component and write the bridge code or you can use the existing React Native components to implement it yourself.

The first option sounds easy, and it is. The second is actually even easier. Looking at what other people have done, you will be amazed of the power of React Native and with how little code you are able to achieve really good results.

One of the missing components, which I needed for our application is a left sliding menu. This is a common pattern in todays mobile apps. Almost any app out there implements it.

What do we need?

Let’s build a simple version of it and then in another article or by yourself you can iterate and make it more complex with more features.

  • Accept two components, left and center.
  • When the center view is dragged to the right, reveal the left view underneath.
  • When dragged to the left the center component hides the other one.
  • Both left and center should be able to contain lists and scroll views without their scrolling to interfere with the revealing mechanism.
  • The component closes or opens automatically and with animation when the user lifts his finger.

I think this is enough to start with and most apps will be happy with it.

The Code

Let’s look at how we have implemented it in our application.

'use strict'

var React = require('react-native')
var {
  Animation,
  PanResponder,
  StyleSheet,
  View,
} = React

var Dimensions = require('Dimensions')
var screenWidth = Dimensions.get('window').width

var SlideMenu = React.createClass({
  componentWillMount: function() {
    this.offset = 0 // Contains the center view offset from the left edge

    this._panGesture = PanResponder.create({
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        return Math.abs(gestureState.dx) > Math.abs(gestureState.dy)
               && Math.abs(gestureState.dx) > 10
      },
      onPanResponderGrant: (evt, gestureState) => this.left = 0,
      onPanResponderMove: (evt, gestureState) => this.moveCenterView(gestureState.dx),
      onPanResponderRelease: this.moveFinished,
      onPanResponderTerminate: this.moveFinished,
    })
  },

  moveCenterView: function(left) {
    if (!this.center) return

    if ((this.offset + left) < 0) {
      this.left = -this.offset
    } else {
      this.left = left
    }

    this.center.setNativeProps({left: this.offset + this.left})
  },

  moveFinished: function() {
    if (!this.center) return

    var offset = this.offset + this.left

    if (this.offset === 0) {
      if (offset > screenWidth * 0.25) {
        this.offset = screenWidth * 0.75
      }
    } else {
      if (offset < screenWidth * 0.5) {
        this.offset = 0
      }
    }

    Animation.startAnimation(this.center, 400, 0, 'easeInOut', {'anchorPoint.x': 0, 'position.x': this.offset})
    this.center.setNativeProps({left: this.offset})
  },

  render: function() {
    var centerView = this.props.renderCenterView ? this.props.renderCenterView() : null
    var leftView = this.props.renderLeftView ? this.props.renderLeftView() : null

    return (
      <View style={[styles.container, this.props.style]}>
        <View style={styles.left}>
          {leftView}
        </View>
        <View
          style={[styles.center, {left: this.offset}]}
          ref={(center) => this.center = center}
          {...this._panGesture.panHandlers}>
          {centerView}
        </View>
      </View>
    )
  },
})

var styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  center: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  left: {
    position: 'absolute',
    top:0,
    left:0,
    bottom:0,
    right: 0,
    backgroundColor: '#FFFFFF',
  },
})

module.exports = SlideMenu

How it works?

As you can see from the code above it is very short. To build the sliding menu, we only need 5 existing components from React Native.

  • Animation - Used to finish the center view dragging movement
  • PanResponder - Handle the user dragging
  • StyleSheet - Calculate the right size and positioning
  • View - Create containers for the center and left view
  • Dimensions - Get the screen size

There are two containers views center and left. left is at the bottom, absolutely positioned and never moves. center is on top and can be dragged to reveal the left view which is supposed to contain your menu or maybe something else, there is no real limitation of what it can be.

this.offset contains the center view offset from the left edge at the beginning of the movement while this.left contains how much has the center view been dragged from the offset.

Then there is the following critical code.

onMoveShouldSetPanResponder: (evt, gestureState) => {
  return Math.abs(gestureState.dx) > Math.abs(gestureState.dy)
         && Math.abs(gestureState.dx) > 10
},

It ensures that the center view move only begins if there is a significant horizontal dragging which is bigger than the vertical dragging.

As a result you can have a scroll view or a list view inside the center view and its dragging and scrolling will not interfere with the sliding menu.

Finally when the user is opening the screen and opens it more than 1/4 and lift his finger the rest will open automatically. Also if he closes and pass more than the half screen the rest will close by itself.

How to use it?

If you have looked closely at the code above you could see that the SlideMenu component accepts two attributes renderLeftView and renderCenterView. These should be two functions which return renderable components when called.

Here is an example taken straight from our code base.

'use strict'

var React = require('react-native')
var {
  StyleSheet,
} = React

var SlideMenu = require('../components/slide-menu')
  , Filters = require('./filters')
  , Products = require('./products')

var SearchTab = React.createClass({
  render: function() {
    return (
      <SlideMenu
        renderLeftView = {() => <Filters />}
        renderCenterView = {() => <Products />} />
    )
  },
})

var styles = StyleSheet.create({
})

module.exports = SearchTab

As you can see it’s very simple to use and implement.

What is missing and can be improved

The SlideMenu component is short and simple but it can be easily improved. Here are some things I suppose I will do next and I can share it in another article.

  • Sliding from the right
  • Support simultaneously left and right menus
  • Support orientation change
  • Open/Close animated with a method call

These are just some of the things I can think of right now and I am sure you have many more ideas. You can use the code from above as your starting point if you wish.

Next

The next step for you can be to use the code I shared and try to enhance it. For me the next step is to create an even better version which can be useful and used in more cases.

Did you like this article?

Please share it
Enter your email and get our NPM Cheat Sheet for NodeJS Developers and the links to our 5 most popular articles which have helped thousands of developers build faster, more reliable and easier to maintain Node applications.

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2018 Terlici Ltd · Terms · Privacy