Navigation indicators

Earlier in the chapter, you were introduced to the <ActivityIndicator> component. In this section, you'll learn how it can be used when navigating an application that loads data. For example, the user navigates from page (scene) one to page two. However, page two needs to fetch data from the API to display for the user. So while this network call is happening, it makes more sense to display a progress indicator instead of a screen devoid of useful information.

Doing this is actually kind of tricky, because we have to make sure that the data required by the screen is fetched from the API each time the user navigates to the screen. So, we have two goals in mind here:

  • Have the Navigator component automatically fetch API data for the scene that's about to be rendered.
  • Use the promise that's returned by the API call as a means to display the spinner and hide it once the promise has resolved.

Since our scene components probably don't care about whether or not a spinner is displayed, let's implement this as a generic higher-order component:

import React, { Component, PropTypes } from 'react'; 
import { 
  View, 
  ActivityIndicator, 
} from 'react-native'; 
 
import styles from './styles'; 
 
// Wraps the "Wrapped" component with a stateful component 
// that renders an "<ActivityIndicator>" when the "loading" 
// state is true. 
const loading = Wrapped => 
  class LoadingWrapper extends Component { 
    static propTypes = { 
      promise: PropTypes.instanceOf(Promise), 
    } 
 
    state = { 
      loading: true, 
    } 
 
    // Adds a callback to the "promise" that was 
    // passed in. When the promise resolves, we set 
    // the "loading" state to false. 
    componentDidMount() { 
      this.props.promise.then( 
        () => this.setState({ loading: false }), 
        () => this.setState({ loading: false }) 
      ); 
    } 
 
    // If "loading" is true, render the "<ActivityIndicator>" 
    // component. Otherwise, render the "<Wrapped>" component. 
    render() { 
      return new Map([ 
        [true, ( 
          <View style={styles.container}> 
            <ActivityIndicator size="large" /> 
          </View> 
        )], 
        [false, ( 
          <Wrapped {...this.props} /> 
        )], 
      ]) 
      .get(this.state.loading); 
    } 
  }; 
 
export default loading; 

This loading() function takes a component—the Wrapped argument and returns a LoadingWrapper component. The returned wrapper accepts a promise property, and when it's resolved, it changes the loading state to false. As you can see in the render() method, the loading state determines whether the spinner is rendered or the Wrapped component.

With the loading() higher-order function in place, let's take a look at one of our scene components to see how it's used:

import React, { PropTypes } from 'react'; 
import { View, Text } from 'react-native'; 
 
import styles from '../styles'; 
import loading from '../loading'; 
import second from './second'; 
import third from './third'; 
 
// Renders links to other scenes... 
const First = ({ navigator }) => ( 
  <View style={styles.container}> 
    <Text 
      style={styles.item} 
      onPress={() => navigator.replace(second)} 
    > 
      Second 
    </Text> 
    <Text 
      style={styles.item} 
      onPress={() => navigator.replace(third)} 
    > 
      Third 
    </Text> 
  </View> 
); 
 
First.propTypes = { 
  navigator: PropTypes.object.isRequired, 
}; 
 
// Simulates a real "fetch()" call by returning a promise 
// that's resolved after 1 second. 
const fetchData = () => new Promise( 
  resolve => setTimeout(resolve, 1000) 
); 
 
// The exported "Scene" component is composed with 
// higher-order "loading()" function. 
export default { 
  Scene: loading(First), 
  fetchData, 
}; 

This module exports a Scene component and and a fetchData() function that talks to the API. The loading() function we created earlier is used here. It wraps the First component so that a spinner is displayed while the fetchData() promise is pending. The last step is getting that promise into the component whenever the user navigates to a given page. This happens in the renderScene() function in the main module:

import React, { Component } from 'react'; 
import { 
  AppRegistry, 
  Navigator, 
} from 'react-native'; 
 
import first from './scenes/first'; 
 
// The "<route.Scene>" component gets a promise property 
// passed to it, by calling "route.fetchData()". This 
// promise is what controls the progress indicator display. 
const renderScene = (route, navigator) => ( 
  <route.Scene 
    promise={route.fetchData()} 
    navigator={navigator} 
  /> 
); 
 
const NavigationIndicators = () => ( 
  <Navigator 
    initialRoute={first} 
    renderScene={renderScene} 
  /> 
); 
 
AppRegistry.registerComponent( 
  'NavigationIndicators', 
  () => NavigationIndicators 
); 

As you can see, the fetchData() function for any given route is called just before it's rendered, and this is how the promise property is set. Now when you navigate between screens, you'll see a spinner displayed in the middle of the screen that looks just like the first example in this chapter, until the promise resolves.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.255.116