Chapter 4: Navigation

Full Stack React Native

link Bitcoins App

Congratulations on completing unit one! Welcome to unit two, where we introduce mutation, pagination, and navigation.

Our current project is a bitcoins app. It will include the following features:

  • Browse a list of bitcoin prices.
  • Save bitcoins to a list of favorites.

Let's begin the bitcoins app with the Expo init command. By now you should already have the expo-cli installed globally.

expo init bitcoins-app

In the first prompt, be sure to select tabs:

? Choose a template: 
  ----- Managed workflow -----
  blank                 a minimal app as clean as an empty canvas 
  blank (TypeScript)    same as blank but with TypeScript configuration 
❯ tabs                  several example screens and tabs using react-navigation

In the second prompt, name your new bitcoins app.

If you followed these steps successfully, you should have a new project with the following folders:

.
├── App.js
├── __tests__
│   ├── App-test.js
│   └── __snapshots__
├── app.json
├── assets
│   ├── fonts
│   └── images
├── babel.config.js
├── components
│   ├── StyledText.js
│   ├── TabBarIcon.js
│   └── __tests__
├── constants
│   ├── Colors.js
│   └── Layout.js
├── navigation
│   ├── AppNavigator.js
│   ├── BottomTabNavigator.js
│   └── useLinking.js
├── node_modules
├── package.json
├── screens
│   ├── HomeScreen.js
│   └── LinksScreen.js
├── yarn-error.log
└── yarn.lock

link Home Screen

The home screen will be a list view of bitcoins. We will use FlatList to render coin data. Each row will be a pressable button, that brings users to the coin details page.

Editing the HomeScreen.js, we remove the boilerplate code, and add a list of coins:

Grab the full `HomeScreen.js` source code.
import React from 'react';
import {
  Platform,
  ScrollView,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
  Image,
  FlatList
} from 'react-native';

import { MonoText } from '../components/StyledText';

const coins = [{
    "id": 1,
    "name": "Bitcoin",
    "symbol": "BTC",
    "price": "$ 1,012.49",
    "imageUrl":"https://www.cryptocompare.com/media/19633/btc.png"
  },
  {
    "id": 2,
    "name": "Ethereum",
    "symbol": "ETH",
    "price": "$ 186.49",
    "imageUrl":"https://www.cryptocompare.com/media/20646/eth_logo.png",
  },
  {
    "id": 3,
    "name": "Litecoin",
    "symbol": "LTC",
    "price": "$ 72.52",
    "imageUrl": "https://www.cryptocompare.com/media/35309662/ltc.png"
}];

export default function HomeScreen(props) {
  return (
    <View style={styles.container}>
      <FlatList
        style={styles.container}
        contentContainerStyle={styles.contentContainer}
        data={coins}
        keyExtractor={(item, index) => {
          return `${index}`;
        }}
        renderItem={(coin, index) => {
          const {item} = coin;
          return <Text style={{color: "#fff"}}>{item.symbol} - {item.name}</Text>
          } 
        } 
      />
    </View>
  );
}

HomeScreen.navigationOptions = {
  title: "Home",
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#161616',
  },
  contentContainer: {
    paddingTop: 10,
    paddingBottom: 85
  },
});

By this point, you should have a list of three simple <Text /> components.

What is this FlatList?

FlatList is a performant interface for rendering basic, flat lists, supporting the most handy features.

If you ever need to render a list of data, this should be your go-to component. We could try to map over our list like so:

{coins.map((item, index) => <Text style={{color: "#fff"}}>{item.symbol} - {item.name}</Text>)}

But as the list grows, this becomes less performant than our original, immutable flat list.

link Coin Component

This looks okay so far, but the coin details could use some styling. Let's create a functional component called Coin.js.

Add a new Coin.js file into your components directory. Paste in the following snippet:

Paste in the following snippet:
import React, { useState } from 'react';
import {
  Dimensions,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native';

export default function Coin(props) {

  const { coin, onPress } = props;
  const {
    symbol,
    name,
    price,
  } = coin;

  return (
    <TouchableOpacity
      style={styles.container}
      onPress={() => onPress && onPress(coin)}
    >
      <View style={styles.row}>
        <Text style={styles.text} numberOfLines={1}>
          {symbol}
        </Text>
        <View style={styles.right}>
          <Text style={styles.text} numberOfLines={1}>
            {price}
          </Text>
        </View>
      </View>

      <View style={styles.row}>
        <Text style={[styles.text, styles.name]} numberOfLines={1}>
          {name}
        </Text>
      </View>
    </TouchableOpacity>
  );

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 10,
  },
  active: {
    backgroundColor: 'rgba(255,255,255,0.05)',
  },
  row: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  right: {
    flex: 1,
    alignSelf: 'flex-end',
    alignItems: 'flex-end',
  },
  text: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '500',
  },
  name: {
    color: 'rgba(255,255,255,0.5)',
    fontSize: 12,
    fontWeight: '300',
  },
});

In order to display the coin inside HomeScreen.js, make the following changes:


+ import Coin from '../components/Coin';

export default function HomeScreen(props) {
+ const { navigation } = props;
  return (
    <View style={styles.container}>
      <FlatList
        style={styles.container}
        contentContainerStyle={styles.contentContainer}
        data={coins}
        keyExtractor={(item, index) => {
          return `${index}`;
        }}
        renderItem={(coin, index) => {
          const {item} = coin;
-         return <Text style={{color: "#fff"}}>{item.symbol} - {item.name}</Text>
+         return <Coin coin={item} onPress={() => navigation.navigate('Detail', {coin: item})} />
          } 
        } 
      />
    </View>
  );
}

With these changes done, we see a list of three <Coins /> components.

link CoinDetail Screen

Let's navigate to a new screen called CoinDetail.

In the screens directory, create a new file called CoinDetail.js. Insert the following source code:

Click here for the `CoinDetail` source code.
import React, { useState } from 'react';
// Optional: try the useRoute() hook to recieve params.
// import { useRoute } from '@react-navigation/native'; 
import {
  StyleSheet,
  Text,
  Image,
  TouchableOpacity,
  View,
  ScrollView,
} from 'react-native';

export default function CoinDetail({ route }) {
  const { params } = route;
  const { coin } = params;
  const {
    symbol,
    name,
    price,
    imageUrl,
  } = coin;
  return (
    <View style={styles.container}>
        <View style={styles.header}>
          <Image style={styles.image} source={{uri: imageUrl}} />
          <Text numberOfLines={1} style={styles.text}>{symbol} - {name}</Text>
        </View>
        <View style={styles.statsContainer}>
          <ScrollView>
            <View style={styles.statRow}>
              <Text style={styles.stat} numberOfLines={1}>Price</Text>
              <Text style={styles.stat} numberOfLines={1}>{price}</Text>
            </View>
          </ScrollView>
        </View>
    </View>
  );

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  header: {
    flex: 38,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 32,
    color: '#161616',
  },
  image: {
    width: 60,
    height: 60,
    resizeMode: 'cover',
  },
  statsContainer: {
    flex: 62,
    backgroundColor: '#161616',
  },
  statRow: {
    padding: 10,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  stat: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '500',
  },
});

In order to wire up our navigation correctly, we need to import the CoinDetail screen inside App.js:

+import CoinDetail from './screens/CoinDetail';

{/* ... */}

return (
  <View style={styles.container}>
    {Platform.OS === 'ios' && <StatusBar barStyle="default" />}
    <NavigationContainer
      ref={containerRef}
      initialState={initialNavigationState}
    >
      <Stack.Navigator>
        <Stack.Screen name="Root" component={BottomTabNavigator} />
+        <Stack.Screen name="Detail" component={CoinDetail} />
      </Stack.Navigator>
    </NavigationContainer>
  </View>
);

Navigation Stack

Each time you press a bitcoin, it will now bring you to that coin's detail page.

Each detail page is added to the navigation stack. Going back removes it from the navigation stack.

There are two pieces to this:

  • Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function:
/* 1. Navigate to the Details route with params */
navigation.navigate('RouteName', { 
  itemId: 86,
  otherParam: 'anything you want here', 
})
  • Read the params in your screen component:
function DetailsScreen({ route, navigation }) {
  /* 2. Get the param */
  const { itemId } = route.params;
  const { otherParam } = route.params;

Source

Well done!

Wrapping Up

Today we created the bitcoins app with tabbed navigation. Up next, we will set up the bitcoins API.

format_list_bulleted
help_outline