A simple React Native Selection List

A simple React Native Selection List

A few weeks ago I was looking for a component that transitioned to a list scene allow the user to make a selection from the list and transition back. While looking for such component, I stumbled onto a couple of interesting components that solved this problem a little differently and I really liked how easy it was to implement them. react-native-modal-dropdown and react-native-list-view-select are components would strongly consider using in my future projects. However, I couldn't find the component I was looking for so I built my own, using a router and a ListView component.

Settings up scenes

First, let's clear up some terminology. When I mention scene I am referring to a route or a page in web terms. To build my scenes I am going to be using react-native-router-flux as my scene router. The react-native-router-flux is based on updated React Native Navigation API and uses flux architecture to manage router state, you also get the navbar and back button for free 👍. However, this can also be easily implemented using Animated views if you don't have a router.

There are two main scenes:

I will go over how I setup each scene for this example and feel free to check out the router documentation for more information on the components I used.

index.ios.js

import React, { Component } from 'react'
import { AppRegistry, StyleSheet } from 'react-native'
import { Router, Scene } from 'react-native-router-flux'

import FormScene from '@components/FormScene'
import SelectableListScene from '@components/SelectableListScene'

export default class ReactNativeSelectableList extends Component {
  render() {
    return (
      <Router>
        <Scene key="root">
          <Scene
            key="index"
            component={FormScene}
            navigationBarStyle={styles.navBar}
            titleStyle={styles.title}
            title="Grok React"
            initial
          />
          <Scene
            key="selectableList"
            component={SelectableListScene}
            navigationBarStyle={styles.navBar}
            titleStyle={styles.title}
          />
        </Scene>
      </Router>
    )
  }
}

const styles = StyleSheet.create({
  navBar: {
    backgroundColor: '#132D3D',
  },
  title: {
    color: '#F79E31',
    fontWeight: '900'
  }
})

AppRegistry.registerComponent('ReactNativeSelectableList', () => ReactNativeSelectableList)

Setting up Selectable List scene

Now let's setup the SelectableListScene. This scene will display the list of items that a user can select from. This scene will render a component that requires two props:

By requiring these props I allow the parent to determine the list of items shown and how to handle their touch events. Additionally, the parent can have multiple instances of this scene component on the screen. The data structure of the list data will determine what needs to be sent back to the parent and how this data is displayed. In my example I have a flat list of items ['item1', 'item2' , ...] so I simply pass the string value back to the parent. This component can easily be converted to a "stateless component", or sometimes called a "pure function component" by constructing the DataSource for the ListView in the parent scene and passing that data source object down to the SelectableListScene as a prop.

app/components/SelectableListScene.js

import React, { Component } from 'react'
import { ListView, Text, TouchableOpacity, StyleSheet, View } from 'react-native'

export default class SelectableListScene extends Component {
  static defaultProps = {
    list: []
  }

  static propTypes = {
    list: React.PropTypes.array.isRequired,
    onPressAction: React.PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props)

    const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2})
    const { list } = props
    this.state = {
      list: ds.cloneWithRows(list)
    }
  }

  renderRow = (rowData) => {
    const { onPressAction } = this.props
    return (
      <TouchableOpacity onPress={() => onPressAction(rowData)}>
        <View style={styles.itemContainer}>
          <Text style={styles.text}>{rowData}</Text>
        </View>
      </TouchableOpacity>
    )
  }

  render() {
    const { list } = this.state
    return (
      <ListView
        style={styles.container}
        dataSource={list}
        renderRow={this.renderRow}
      />
    )
  }
}

const styles = StyleSheet.create({
  container: {
    marginTop: 60,
  },
  itemContainer: {
    flex: 1,
    flexDirection: 'row',
    padding: 10,
    borderTopWidth: 0.5,
    borderBottomWidth: 0.5,
    borderColor: '#4488A7',
  },
  text: {
    color: '#132d3d',
  }
})

Setting up a Form scene

An example use case for the selectable list component is with a form. The form in my example, will consist of React Native components such as:

I will also need the Actions helper from react-native-router-flux to transition to and from SelectableListScene component. I construct the component state with selectableList and selectedItem properties. Next I am setting my selectedItem state property to use a prop if provided or default to null. I am relying on the null to display placeholder text in the form. Notice that I am not setting placeholder as the state value, because I only want to keep real value in the state.

Pro tip: If the data for selectableList prop needs to be fetched from an endpoint. Then the state can be initialized as an empty array in the constructor. Then the state can be hydrated though componentWillReceiveProps().

Notice that we are passing the list and the onPressAction props to the Actions.selectableList() scene when we transition. The onPressAction contains the setState function to set the state on the parent component and will call Actions.pop() to pop back to the main scene. As I mentioned we get the navbar and the back button for free with the router so when the user hits back button on the navbar it navigates the user back to main scene.

app/components/FormScene.js

import React, { Component } from 'react'
import {
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TextInput,
  View,
  TouchableOpacity,
} from 'react-native'

import { Actions } from 'react-native-router-flux'

export default class FormScene extends Component {
  constructor(props) {
    super(props)

    this.state = {
      name: '',
      selectableList: ['React is awesome', 'React is easy', 'React is the best'],
      selectedItem: props.selectedItem || null,
    }
  }

  render() {
    const { name, selectableList, selectedItem } = this.state
    return (
      <ScrollView style=>
        <StatusBar barStyle="light-content" />
        <View style={styles.formField}>
          <TextInput style={styles.inputField} placeholder="Your name" value={name} onChangeText={(text) => this.setState({name: text})}/>
        </View>
        <TouchableOpacity
          onPress={() => Actions.selectableList({
            list: selectableList,
            onPressAction: (selectedItem) => { this.setState({ selectedItem }); Actions.pop() },
            title: "How do you feel about react?"
          })}
        >
          <View style={[styles.formField, { flexDirection: 'row', justifyContent: 'space-between' }]} >
            <Text style=>{ selectedItem || 'How do you feel about react?' }</Text>
            <Text style=>></Text>
          </View>
        </TouchableOpacity>
      </ScrollView>
    )
  }
}

const styles = StyleSheet.create({
  formField: {
    alignItems: 'center',
    minHeight: 40,
    borderBottomWidth: 0.5
  },
  inputField: {
    height: 40,
  }
})

Plug and pray

At this point my SelectableListScene is ready to be used throughout the app all I need is the list and the onPressAction props. Taping on the selectable field will transition to a new scene and display the list of items. The onPressAction prop will transition the user back to the FormScene set the value in the selectedItem state. If the user can hits the back icon in the navbar they will transition back to the previous scene the state will be left unchanged. You can make the component even more modular by providing a renderRow prop where the parent will control how each item is rendered.

I encourage you to take a look at the source of my example app here react-native-selectable-list and leave a comment if you have any questions or comments.

Categories: Software Development | Tags: React Native, React, Components, Selection, ListView, Selectable, List, Router

Portrait photo for Anton Domratchev Anton Domratchev

Anton graduated from the University of Texas at San Antonio with a degree in Cyber Security. Anton started front end development with HTML, CSS and JavaScript in 2008, and has been developing in PHP since 2013. He is currently honing his Ruby on Rails skills while discovering the wonderful world of Software Architecture.

Comments


LET US HELP YOU!

We provide a free consultation to discover competitive advantages for your business. Contact us today to schedule an appointment.