Aven
Intro to React Native »

Chapter 5: React Navigation

Now we will make our app change screens and respond to the back button

React Navigation

A library that provides navigation components and behavior for mobile apps.

Get Started

Lets follow the setup steps for React Navigation.

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
npm install @react-navigation/native @react-navigation/stack

Now, we can set up our app to use a stack navigator:

App.js
import React from "react";
import * as eva from "@eva-design/eva";
import HomeScreen from "./HomeScreen";
import CityScreen from "./CityScreen";
import { ApplicationProvider, IconRegistry } from "@ui-kitten/components";
import { EvaIconsPack } from "@ui-kitten/eva-icons";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

const THEME = {
  ...eva.light,
  "color-primary-100": "#F2F6FF",
  "color-primary-200": "#e9E4FF",
  "color-primary-300": "#c6C1FF",
  "color-primary-400": "#898BFF",
  "color-primary-500": "#7366FF",
  "color-primary-600": "#574BDB",
  "color-primary-700": "#4A34B8",
  "color-primary-800": "#402694",
  "color-primary-900": "#291C7A",
};

const Stack = createStackNavigator();

export default () => (
  <>
    <IconRegistry icons={EvaIconsPack} />
    <ApplicationProvider {...eva} theme={THEME}>
      <NavigationContainer>
        <Stack.Navigator headerMode="none">
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="City" component={CityScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </ApplicationProvider>
  </>
);

Lets create a new CityScreen.js:

CityScreen.js
import React from "react";
import { Layout, Text } from "@ui-kitten/components";
import { StyleSheet } from "react-native";

export default function CityScreen({ route }) {
  const { city } = route.params;
  return (
    <Layout style={styles.layout}>
      <Text category="h1" status="primary">
        {city.name}
      </Text>
    </Layout>
  );
}

const styles = StyleSheet.create({
  layout: {
    flex: 1,
    marginHorizontal: 12,
    alignItems: "center",
  },
});

Now our new screen exists, but we have no way to get to it. Lets fix that by adding a press handler to our CityCard component:

HomeScreen.js
import { useNavigation } from "@react-navigation/native";

function CityRow({ city }) {
  // ...
  const { navigate } = useNavigation();
  return (
    <Card
      onPress={() => {
        navigate("City", { city });
      }}
      // ...

Make fit in status bar:

import { SafeAreaView } from "react-native-safe-area-context";
// to use it:
<SafeAreaView style={styles.container}>
 // ...
</SafeAreaView>

Add header

function BackIcon(props) {
  return <Icon {...props} name="arrow-back" />;
}

// inside <SafeAreaView>:
<TopNavigation
  title={city.name}
  alignment="center"
  accessoryLeft={() => (
    <TopNavigationAction icon={BackIcon} onPress={navigateBack} />
  )}
/>
<Divider />

You will also need this function as the button callback for going back:

function navigateBack() {
  navigation.goBack();
}

Lets prepare some icons to use in the header, to switch modes of our city screen:

function MapIcon(props) {
  return <Icon {...props} name="map" />;
}

function InfoIcon(props) {
  return <Icon {...props} name="info" />;
}

Now add state inside the screen:

CityScreen.js
  const [isMapView, setIsMapView] = useState(false);

    <TopNavigation
      ...
        accessoryRight={() =>
          isMapView ? (
            <TopNavigationAction
              icon={InfoIcon}
              onPress={() => {
                setIsMapView(false);
              }}
            />
          ) : (
            <TopNavigationAction
              icon={MapIcon}
              onPress={() => {
                setIsMapView(true);
              }}
            />
          )
        }
      />
      <Divider />
      {isMapView ? <CityMap city={city} /> : <CityStats city={city} />}

Create component to display the city stats:

function useCityStats(city) {
  return {
    avgPM2_5: 111,
    avgTempF: 222,
    avgHumidity: 333,
    sensorCount: 123,
    population: 1234,
  };
}

function CityStats({ city }) {
  const stats = useCityStats(city.cityId);
  return (
    <Layout style={styles.layout}>
      {stats && (
        <>
          <View style={styles.valueRow}>
            <Text category="h4">PM2.5</Text>
            <Text>{stats.avgPM2_5.toFixed(0)}</Text>
          </View>
          <View style={styles.valueRow}>
            <Text category="h4">Temperature</Text>
            <Text>{stats.avgTempF.toFixed(0)}°F</Text>
          </View>
          <View style={styles.valueRow}>
            <Text category="h4">Humidity</Text>
            <Text>{stats.avgHumidity.toFixed(0)}%</Text>
          </View>
          <View style={styles.valueRow}>
            <Text category="h4">Sensor Count</Text>
            <Text>{stats.sensorCount}</Text>
          </View>
          <View style={styles.valueRow}>
            <Text category="h4">Population</Text>
            <Text>{stats.population}</Text>
          </View>
        </>
      )}
    </Layout>
  );
}

And a placeholder map component with a spinner:

function CityMap({ city }) {
  return (
    <Layout style={styles.spinnerLayout}>
      <Spinner />
    </Layout>
  );
}

Next: API Fetching

© Aven LLC and Aven Contributors. Licensed under Creative Commons BY-NC-SA 4.0

Open Source under Apache 2.0