I had an issue where I wanted a Component in my React Native application to show two different ListViews depending on what was being selected.

Additionally I didn't want to make a new API call every time I switched ListViews, and I had to confine this within the same TabBarIOS view without navigating to/from any pages.

Here's how it ended up looking:

Let's dive into how this works.

Collections is going to refer to the view that handles both Featured and All collections. It's the initial route in its own NavigatorIOS component (each tab has its own), and when I switch between Featured and All I have no intention on re-populating the list again from the API call.

The render method of my Collections component looks like this:

  render: function() {
    if (!this.state.loaded) {
      return (
        this.renderLoading()
        )
      }
      return (
          <View style={styles.segmentControl}>
            <SegmentedControlIOS
              values={['Featured', 'All']}
              selectedIndex={0}
              tintColor={'#D6573D'}
              onValueChange={(val) => {
                this.setState({
                  selectedTab: val
                })
              }} />
              {this.renderListView()}
          </View>
        )
  },

If our API call hasn't finished, we'll display a loading screen. Otherwise, we'll return a SegmentedControlIOS component nested in a View with two values, a default index (Featured is in index 0), and a callback that fires whenever the value is changed.

This View will also call renderListView.

Let's take a look at renderListView:

  renderListView: function() {
    if (this.state.selectedTab === 'Featured') {
      return (
        <View style={styles.container}>
          {this.renderFeaturedCollectionsListView()}
        </View>
        )
    } else if (this.state.selectedTab === 'All') {
      return (
          this.renderAllCollectionsListView()
        )
    }
  },

renderListView checks our state for the selectedTab. Note how I'm wrapping renderFeaturedCollectionsListView into a View container, but not renderAllCollectionsListView. I haven't figured out the explanation for this, but I know that my application breaks if I wrap both within a View container.

Each ListView render method looks like this:

  renderFeaturedCollectionsListView: function() {
    return (
      <ListView
        dataSource={this.state.featuredCollectionsDataSource}
        renderRow={this.renderCollectionCell}
        style={styles.collectionListView}
        automaticallyAdjustContentInsets={false}
        contentInset={{bottom: 50}} />
        )
  },

  renderAllCollectionsListView: function() {
    return (
      <ListView
        dataSource={this.state.allCollectionsDataSource}
        renderRow={this.renderCollectionCell}
        style={styles.collectionListView}
        automaticallyAdjustContentInsets={false}
        contentInset={{bottom: 50}}
       />
      )
  },

Note that they grab their own individual dataSource. But where is this data coming from?

Our componentDidMount lifecycle event handles that part:

  componentDidMount: function() {
    api.getFeaturedCollections(this.props.accessToken)
      .then((responseData) => {
        this.setState({
          featuredCollectionsDataSource: this.state.featuredCollectionsDataSource.cloneWithRows(responseData.collections),
          loaded: true
        })
      })
    .then(() => {
      api.getAllCollections(this.props.accessToken)
        .then((responseData) => {
          this.setState({
            allCollectionsDataSource: this.state.allCollectionsDataSource.cloneWithRows(responseData.collections),
          })
        })
    })
    .done()
  },

Additionally, our getInitialState sets our selectedTab property to Featured by default.

Here's how the entire Component looks:

var React = require('react-native');  
var styles = require('./styles.js');

var api = require('../../Utils/api.js');  
var Loading = require('../Loading');  
var CollectionCell = require('./CollectionCell');  
var SingleCollection = require('./SingleCollection');

var {  
 View,
 ListView,
 SegmentedControlIOS
} = React;

var Collections = React.createClass({  
  getInitialState: function() {
    return {
      accessToken: this.props.accessToken,
      featuredCollectionsDataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}),
      allCollectionsDataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}),
      selectedTab: 'Featured',
      loaded: false
    }
  },

  componentDidMount: function() {
    api.getFeaturedCollections(this.props.accessToken)
      .then((responseData) => {
        this.setState({
          featuredCollectionsDataSource: this.state.featuredCollectionsDataSource.cloneWithRows(responseData.collections),
          loaded: true
        })
      })
    .then(() => {
      api.getAllCollections(this.props.accessToken)
        .then((responseData) => {
          this.setState({
            allCollectionsDataSource: this.state.allCollectionsDataSource.cloneWithRows(responseData.collections),
          })
        })
    })
    .done()
  },

  render: function() {
    if (!this.state.loaded) {
      return (
        this.renderLoading()
        )
      }
      return (
          <View style={styles.segmentControl}>
            <SegmentedControlIOS
              values={['Featured', 'All']}
              selectedIndex={0}
              tintColor={'#D6573D'}
              onValueChange={(val) => {
                this.setState({
                  selectedTab: val
                })
              }} />
              {this.renderListView()}
          </View>
        )
  },

  renderLoading: function() {
    return (
      <View style={styles.container}>
        <Loading
          loaded={this.state.loaded} />
      </View>
      )
  },

  renderListView: function() {
    if (this.state.selectedTab === 'Featured') {
      return (
        <View style={styles.container}>
          {this.renderFeaturedCollectionsListView()}
        </View>
        )
    } else if (this.state.selectedTab === 'All') {
      return (
          this.renderAllCollectionsListView()
        )
    }
  },

  renderFeaturedCollectionsListView: function() {
    return (
      <ListView
        dataSource={this.state.featuredCollectionsDataSource}
        renderRow={this.renderCollectionCell}
        style={styles.collectionListView}
        automaticallyAdjustContentInsets={false}
        contentInset={{bottom: 50}} />
        )
  },

  renderAllCollectionsListView: function() {
    return (
      <ListView
        dataSource={this.state.allCollectionsDataSource}
        renderRow={this.renderCollectionCell}
        style={styles.collectionListView}
        automaticallyAdjustContentInsets={false}
        contentInset={{bottom: 50}}
       />
      )
  },

  renderCollectionCell: function(collection) {
    return (
      <CollectionCell
        onSelect={() => this.selectCollection(collection)}
        collection={collection} />
      )
  },

  selectCollection: function(collection) {
    this.props.navigator.push({
      title: collection.name,
      component: SingleCollection,
      backButtonTitle: ' ',
      passProps: {collectionId: collection.id,
                  accessToken: this.state.accessToken}
    })
  }
})

module.exports = Collections;