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:
Navigator
component automatically fetch API data for the scene that's about to be rendered.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.
18.188.255.116