Book Image

React Native Blueprints

By : Emilio Rodriguez Martinez
Book Image

React Native Blueprints

By: Emilio Rodriguez Martinez

Overview of this book

Considering the success of the React framework, Facebook recently introduced a new mobile development framework called React Native. With React Native's game-changing approach to hybrid mobile development, you can build native mobile applications that are much more powerful, interactive, and faster by using JavaScript This project-based guide takes you through eight projects to help you gain a sound understanding of the framework and helps you build mobile apps with native user experience. Starting with a simple standalone groceries list app, you will progressively move on to building advanced apps by adding connectivity with external APIs, using native features, such as the camera or microphone, in the mobile device, integrating with state management libraries such as Redux or MobX, or leveraging React Native’s performance by building a full-featured game. This book covers the entire feature set of React Native, starting from the simplest (layout or navigation libraries) to the most advanced (integration with native code) features. By the end of this book, you’ll be able to build professional Android and iOS applications using React Native.
Table of Contents (15 chapters)
Title Page
Credits
About the Author
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface

Building the ShoppingList screen


Our first screen will contain a list of the items we need to buy, so it will contain one list item per item we need to buy, including a button to mark that item as already bought. Moreover, we need a button to navigate to the AddProduct screen, which will allow us to add products to our list. Finally, we will add a button to clear the list of products, in case we want to start a new shopping list:

Let's start by creating ShoppingList.js inside the screens folder and importing all the UI components we will need from native-base and react-native (we will use an alert popup to warn the user before clearing all items). The main UI components we will be using are Fab (the blue and red round buttons), List, ListItem, CheckBox, Text, and Icon. To support our layout, we will be using Body, Container, Content, and Right, which are layout containers for the rest of our components.

Having all these components, we can create a simple version of our ShoppingList component:

/*** ShoppingList.js ***/

import React from 'react';
import { Alert } from 'react-native';
import {
  Body,
  Container,
  Content,
  Right,
  Text,
  CheckBox,
  List,
  ListItem,
  Fab,
  Icon
} from 'native-base';

export default class ShoppingList extends React.Component {
  static navigationOptions = {
    title: 'My Groceries List'
  };
  /*** Render ***/
  render() {
    return (
      <Container>
        <Content>
          <List>
            <ListItem>
              <Body>
                <Text>'Name of the product'</Text>
              </Body>
              <Right>
                <CheckBox
                  checked={false}
                />
              </Right>
            </ListItem>
          </List>
        </Content>
        <Fab
          style={{ backgroundColor: '#5067FF' }}
          position="bottomRight"
        >
          <Icon name="add" />
        </Fab>
        <Fab
          style={{ backgroundColor: 'red' }}
          position="bottomLeft"
        >
          <Icon ios="ios-remove" android="md-remove" />
        </Fab>
      </Container>
    );
  }
}

This is just a dumb component statically displaying the components we will be using on this screen. Some things to note:

  • navigationOptions is a static attribute which will be used by <Navigator> to configure how the navigation would behave. In our case, we want to display My Groceries List as the title for this screen.
  • For native-base to do its magic, we need to use <Container> and <Content> to properly form the layout.
  • Fab buttons are placed outside <Content>, so they can float over the left and right-bottom corners.
  • Each ListItem contains a <Body> (main text) and a <Right> (icons aligned to the right).

Since we enabled Live Reload in our first steps, we should see the app reloading after saving our newly created file. All the UI elements are now in place, but they are not functional since we didn't add any state. This should be our next step.

Adding state to our screen

Let's add some initial state to our ShoppingList screen to populate the list with actual dynamic data. We will start by creating a constructor and setting the initial state there:

/*** ShoppingList.js ***/

...
constructor(props) {
  super(props);
  this.state = {
    products: [{ id: 1, name: 'bread' }, { id: 2, name: 'eggs' }]
  };
}
...

Now, we can render that state inside of <List> (inside the render method):

/*** ShoppingList.js ***/

...
<List>
 {
   this.state.products.map(p => {
     return (
       <ListItem
         key={p.id}
       >
         <Body>
           <Text style={{ color: p.gotten ? '#bbb' : '#000' }}>
             {p.name}
           </Text>
         </Body>
         <Right>
           <CheckBox
             checked={p.gotten}
            />
         </Right>
       </ListItem>
     );
   }
  )}
</List>
...

We now rely on a list of products inside our component's state, each product storing an id, a name, and gotten properties. When modifying this state, we will automatically be re-rendering the list.

Now, it's time to add some event handlers, so we can modify the state at the users' command or navigate to the AddProduct screen.

Adding event handlers 

All the interaction with the user will happen through event handlers in React Native. Depending on the controller, we will have different events which can be triggered. The most common event is onPress, as it will be triggered every time we push a button, a checkbox, or a view in general. Let's add some onPress handlers for all the components which can be pushed in our screen:

/*** ShoppingList.js ***/

...
render() {
 return (
   <Container>
     <Content>
       <List>
        {this.state.products.map(p => {
          return (
            <ListItem
              key={p.id}
              onPress={this._handleProductPress.bind(this, p)}
            >
              <Body>
                <Text style={{ color: p.gotten ? '#bbb' : '#000' }}>
                  {p.name}
                </Text>
              </Body>
              <Right>
                <CheckBox
                  checked={p.gotten}
                  onPress={this._handleProductPress.bind(this, p)}
                />
              </Right>
            </ListItem>
          );
       })}
       </List>
     </Content>
     <Fab
       style={{ backgroundColor: '#5067FF' }}
       position="bottomRight"
       onPress={this._handleAddProductPress.bind(this)}
     >
       <Icon name="add" />
     </Fab>
     <Fab
       style={{ backgroundColor: 'red' }}
       position="bottomLeft"
       onPress={this._handleClearPress.bind(this)}
     >
       <Icon ios="ios-remove" android="md-remove" />
     </Fab>
   </Container>
   );
 }
...

Notice we added three onPress event handlers:

  • On <ListItem>, to react when the user taps on one product in the list
  • On <CheckBox>, to react when the user taps on the checkbox icon next to every product in the list
  • On both the <Fab> buttons

If you know React, you probably understand why we use .bind in all our handler functions, but, in case you have doubts, .bind will make sure we can use this inside the definition of our handlers as a reference to the component itself instead of the global scope. This will allow us to call methods inside our components as this.setState or read our component's attributes, such as this.props and this.state.

For the cases when the user taps on a specific product, we also bind the product itself, so we can use them inside our event handlers.

Now, let's define the functions which will serve as event handlers:

/*** ShoppingList.js ***/

...
_handleProductPress(product) {
 this.state.products.forEach(p => {
   if (product.id === p.id) {
     p.gotten = !p.gotten;
   }
   return p;
 });

 this.setState({ products: this.state.products });
}
...

First, let's create a handler for when the user taps on a product from our shopping list or in its checkbox. We want to mark the product as gotten (or unmark it if it was already gotten), so we will update the state with the product marked properly.

Next, we will add a handler for the blue <Fab> button to navigate to the AddProduct screen:

/*** ShoppingList.js ***/

...
_handleAddProductPress() {
  this.props.navigation.navigate('AddProduct', {
    addProduct: product => {
      this.setState({
        products: this.state.products.concat(product)
      });
    },
    deleteProduct: product => {
      this.setState({
        products: this.state.products.filter(p => p.id !== product.id)
      });
    },
    productsInList: this.state.products
  });
}
...

This handler uses this.props.navigation, which is a property automatically passed by the Navigator component from react-navigation. This property contains a method named navigate, receiving the name of the screen to which the app should navigate plus an object which can be used as a global state. In the case of this app, we will store three keys:

  • addProduct: One function to allow the AddProduct screen to modify the ShoppingList component's state to reflect the action of adding a new product to the shopping list.
  • deleteProduct: One function to allow the AddProduct screen to modify the ShoppingList component's state to reflect the action of removing a product from the shopping list.
  • productsInList: A variable holding the list of products is already on the shopping list, so the AddProducts screen can know which products were already added to the shopping list and display those as "already added", preventing the addition of duplicate items.

Handling state within the navigation should be seen as a workaround for simple apps containing a limited number of screens. In larger apps (as we will see in later chapters), a state management library, such as Redux or MobX, should be used to keep the separation between pure data and user interface handling.

We will add the last handler for the blue <Fab> button, which enables the user to clear all the items in the shopping list in case you want to start a new list:

/*** ShoppingList.js ***/

...
_handleClearPress() {
  Alert.alert('Clear all items?', null, [
    { text: 'Cancel' },
    { text: 'Ok', onPress: () => this.setState({ products: [] }) }
  ]);
}
...

We are using Alert to prompt the user for confirmation before clearing all the elements in our shopping list. Once the user confirms this action, we will empty the products attribute in our component's state.

Putting it all together

Let's see how the whole component's structure would look like when putting all the methods together:

/*** ShoppingList.js ***/

import React from 'react';
import { Alert } from 'react-native';
import { ... } from 'native-base';

export default class ShoppingList extends React.Component {
 static navigationOptions = {
   title: 'My Groceries List'
 };

 constructor(props) {
   ...
 }

 /*** User Actions Handlers ***/
 _handleProductPress(product) {
   ...
 }

 _handleAddProductPress() {
   ...
 }

 _handleClearPress() {
   ...
 }

 /*** Render ***/
 render() {
   ...
 }
}

The structure of a React Native component is very similar to a normal React component. We need to import React itself and then some components to build up our screen. We also have several event handlers (which we have prefixed with an underscore as a mere convention) and finally a render method to display our components using standard JSX.

The only difference with a React web app is the fact that we are using React Native UI components instead of DOM components.