react-workshop

React Native Workshop

Welcome to the React Native Guide for the VOID Tech Org at the University of Michigan. This tutorial introduces the basics of React Native and how to use it to build a simple mobile application.

Disclaimer

This tutorial is not intended to be a comprehensive guide to React Native. It is intended to be a quick introduction to this framework and how to use it to build a simple mobile application. You can use this guide as reference for your projects, but you should also look at the official documentation for more information.

Prerequisites

This guide assumes you have basic programming fundamentals, such as variables, functions, loops, and classes. You should also have a basic understanding of React. If you are not familiar with React, you can get started with VOID’s TypeScript and React guide. You can also read the React Documentation to learn more about React.

Getting Started

Make sure you are in a Unix environment (Mac or Linux). If you are on Windows, you can use the Windows Subsystem for Linux (WSL) to run a Linux environment on your Windows machine. You can find instructions on how to set up WSL here.

To get started, you will need to download the starter code for the workshop:

Download and unpack the starter files:

$ pwd
/home/santiagomed/umich/void/workshops
$ mkdir react-native-workshop
$ cd react-native-workshop
$ wget https://voidtechmichigan.org/react-native-workshop/starter_files.tar.gz
$ tar -xvzf starter_files.tar.gz

Move the starter files to your project directory and remove the original starter_files/ directory and tarball.

$ pwd
/home/santiagomed/umich/void/workshops/react-native-workshop
$ mv starter_files/* .
$ rm -rf starter_files starter_files.tar.gz

Your project directory should now look like this:

$ tree
react-native-workshop/
├── App.tsx
├── app.json
├── assets
│   ├── adaptive-icon.png
│   ├── favicon.png
│   ├── icon.png
│   └── splash.png
├── babel.config.js
├── components
│   ├── Cart.tsx
│   ├── ItemCard.tsx
│   ├── ItemFactory.tsx
│   └── Market.tsx
├── global
│   └── types.ts
├── helper
│   └── api.ts
├── package-lock.json
├── package.json
├── styles
│   ├── cart.ts
│   ├── itemCard.ts
│   ├── itemFactory.ts
│   └── market.ts
└── tsconfig.json

Installation

Once you have cloned the repository, you will need to install Node.js and the dependencies for the project.

If you already have Node.js installed, use wget to install nvm, the Node.js version manager. You can check if you have Node.js installed by running node --version.

$ node --version
v16.18.0
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash

Then, use nvm to install the latest stable version of Node.js.

$ nvm install --lts

If you do not have Node.js installed, you can install it from the Node.js website. Make sure you install the latest stable version (LTS). As of October 20, 2022, the LTS is v16.18.0. Make sure your installation also includes npm, the Node.js package manager.

If you want to install nvm to manage your Node.js versions, you can do so through the command below. This is not necessary for the workshop, but it is recommended.

$ node --version
v16.18.0
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash

Once you have Node.js installed, you can install the dependencies for the project.

$ pwd
/home/santiagomed/umich/void/workshops/react-native-workshop
$ npm ci

Expo

We are using Expo to build our React Native applications. Expo is a set of tools, libraries, and services that let you build native iOS and Android apps using JavaScript and React. Expo is a great way to get started with React Native and is a great tool for building production-ready applications.

Expo CLI is a command line interface for developing Expo apps. It is installed globally on your computer and is used to create, build, and publish your Expo apps.

Using Expo CLI

You can use Expo CLI without installation by leveraging npx — a Node.js package runner.

See available commands:

$ npx expo -h

  Usage
    $ npx expo <command>

  Commands
    start, export, export:web
    run:ios, run:android, prebuild
    install, customize, config
    login, logout, whoami, register

  Options
    --version, -v   Version number
    --help, -h      Usage info

  For more info run a command with the --help flag
    $ npx expo start --help

Create an expo account (will redirect you to the browser):

$ npx expo register

Login to your expo account after creating it:

$ npx expo login

Using the Expo Go app

Install Expo Go on your mobile device and log in with your account. Expo Go is a free, open-source client for running Expo apps on Android and iOS. It is available on the App Store and Google Play.

Running the app

Start the development server with npm start or npx expo start within the project directory. This will display a QR code in your terminal.

To open the app:

Once you run the app, you should see “Hello VOID!” on your device.

Learn more about Expo at docs.expo.dev.

React Native Overview

Before continuing with the rest of the guide, please go over VOID’s TypeScript and React guide here. This will help you understand the basics of these technologies and how to use them, which is necessary to understand React Native.

React Native is a JavaScript framework for writing real, natively rendering mobile applications for iOS and Android. It is developed by Facebook and a community of individual developers and companies. React Native lets you build mobile apps using only JavaScript (or TypeScript). It uses the same design as React, letting you compose a rich mobile UI from declarative components. To learn more about React Native, check out the React Native website.

Components

Components are the building blocks of React Native apps. They are the functions that return a piece of the UI. We use JSX to describe this UI. JSX is a syntax extension of JavaScript; it is similar to HTML, but it is not HTML. We go over JSX in more detail in the React guide.

From the React Native website:

In Android development, you write views in Kotlin or Java; in iOS development, you use Swift or Objective-C. With React Native, you can invoke these views with JavaScript using React components. At runtime, React Native creates the corresponding Android and iOS views for those components. Because React Native components are backed by the same views as Android and iOS, React Native apps look, feel, and perform like any other apps. We call these platform-backed components Native Components.

React Native comes with a set of essential, ready-to-use Native Components you can use to start building your app today. These are React Native’s Core Components.

Some of the React Native Core Components you are going to be using are:

Component Android iOS HTML Description
<View> <ViewGroup <UIView> <div> A container that supports layout with flexbox, style, some touch handling, and accessibility controls, and is designed to be nested inside other views and to have 0 to many children of any type.
<Text> <TextView> <UITextView> <p> Displays, styles, and nests strings of text and even handles touch events.
<Image> <ImageView> <UIImageView> <img> Displays different types of images, including network images, static resources, temporary local images, and images from local disks, such as the camera roll.
<ScrollView> <ScrollView> <UIScrollView> <div> A generic scrolling container that can contain multiple components and views.
<TextInput> <EditText> <UITextField> <input> Allows the user to enter text.
<Touchable*> <View> <UIView> <div>/<button> A wrapper for making views respond properly to touches. The Touchable* components can be used to make any view respond properly to touches: <TouchableHighlight>, <TouchableOpacity>, <TouchableWithoutFeedback>, <TouchableNativeFeedback>, <TouchableBounce>, <Pressable>.

We can also build our own components to describe what you want to see on the screen. A component takes in parameters, called props (short for “properties”), and returns a hierarchy of views to display via the render method.

Let’s take a look at App.tsx

import * as React from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, View } from "react-native";
import Market from "./components/Market";

export default function App() {
  return (
    <View style={styles.container}>
      <Market />
      <StatusBar style="auto" /> {/* Expo status bar for debugging */}
    </View>
  );
}

The App component is our main component for the App. We can think of it as our entry point. Inside, we are returning a View component, styling it and then calling our Market component. The Market component is a custom component that we created to display the market data and logic.

Custom Components

Similar to React, we can return JSX from our components. Let’s take a look at the JSX in the Market component.

return (
    <KeyboardAvoidingView
      behavior={Platform.OS === "ios" ? "padding" : "height"}
    >
      <SafeAreaView style={styles.container}>
        <Text style={styles.title}>Market</Text>
        <ScrollView
          style={styles.scrollView}
          refreshControl={
            <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
          }
        >
          <View style={styles.itemCards}>
            {items.map((item, index) => (
              <View key={index}> {/* This is a key for React to keep track of the elements */}
                <ItemCard
                  item={item}
                  inCart={false}
                  handleCart={handleAddToCart}
                  disabled={cart.some((cartItem) => cartItem.id === item.id)}
                />
              </View>
            ))}
          </View>
          <Text style={styles.title}>Cart</Text>
          <Cart
            items={cart}
            handleRemoveFromCart={handleRemoveFromCart}
            handleCheckout={handleCheckout}
          />
          <Text style={styles.title}>Add Item</Text>
          <ItemFactory handleAddItem={handleAddItem} />
        </ScrollView>
      </SafeAreaView>
    </KeyboardAvoidingView>
  );

There’s a lot going on here so let’s go over each component in our code.

KeyboardAvoidingView

This component is used to make sure that the keyboard does not cover up the input fields when the user is typing. It is a wrapper component that can be used to make any view respond properly to the keyboard.

SafeAreaView

This component is used to make sure that the content is not blocked by the device’s status bar or notches. For example, on an iPhone X or newer, the content will be displayed below the notch.

ScrollView

This component is used to make sure that the content is scrollable. It is a generic scrolling container that can contain multiple components and views.

RefreshControl

This component is used to make sure that the content is refreshable. It is a wrapper component that can be used to make any view respond properly to the refresh gesture. We’ll go over refreshing in more detail later.

ItemCard

This is a custom component that we created to display the item data and logic. We won’t go over this component in detail, but you can take a look at how it works in VOID’s React guide. You can also take a look at the React Native implementation for this component in the GitHub repo. The code is also below.

Cart

This is a custom component that we created to display the cart data and logic. We won’t go over this component in detail, but you can take a look at how it works in VOID’s React guide. You can also take a look at the React Native implementation for this component in the GitHub repo.

ItemFactory

This is a custom component that we created to display the form to create items and its logic. We won’t go over this component in detail, but you can take a look at how it works in VOID’s React guide. You can also take a look at the React Native implementation for this component in the GitHub repo.

Note: The links above to VOID’s React guide are written in regular React, but the concepts are the same.

Styling

React Native only takes care of our front-end application logic and rendering. It does not handle styling. We can use the StyleSheet API to create stylesheets and apply them to our components. Let’s take a look at a sample stylesheet from ItemCard.ts:

import { StyleSheet } from "react-native";

const styles = StyleSheet.create({
  container: {
    width: 170,
    height: 150,
    backgroundColor: "#fff",
    borderRadius: 10,
    margin: 10,
    padding: 20,
    alignItems: "center",
    justifyContent: "space-between",
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  textContainer: {
    width: "100%",
    height: "20%",
    alignItems: "center",
    justifyContent: "center",
  },
  name: {
    fontSize: 16,
    fontWeight: "bold",
    textAlign: "center",
  },
  price: {
    fontSize: 14,
    color: "#666",
    textAlign: "center",
  },
  calories: {
    fontSize: 14,
    color: "#666",
    textAlign: "center",
  },
  addToCartButton: {
    width: "100%",
    height: "50%",
    backgroundColor: "#28bbd7",
    alignItems: "center",
    justifyContent: "center",
    borderRadius: 10,
  },
  removeFromCartButton: {
    width: "100%",
    height: "50%",
    backgroundColor: "#f69c85",
    alignItems: "center",
    justifyContent: "center",
    borderRadius: 10,
  },
});

export default styles;

Again this is a lot of code. Take a minute to look at all the implemented styles and try to figure out what each is doing. Basically, what we’re doing here is creating a JavaScript object that contains all the styles that we want to apply to our components. We specify the style by setting certain properties we want our component to have, such as width or its location on the screen, using justifyContent and alignItems. Most names are self explanatory, but if you’re still confused, you can take a look at the React Native documentation for more information. I certainly do not know all the properties that can be used, so I usually just look them up when I need them.

We can then import this stylesheet into our component and apply it to our component by using the style prop. For example, in ItemCard.tsx:

import * as React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { Item } from "../global/types";

import styles from "../styles/itemCard";

interface ItemCardProps {
  item: Item;
  inCart: boolean;
  handleCart: (item: Item) => void;
  disabled?: boolean;
}

const ItemCard = ({ item, inCart, handleCart, disabled }: ItemCardProps) => {
  return (
    <View style={styles.container}>
      <View style={styles.textContainer}>
        <Text style={styles.name}>{item.name}</Text>
        <Text style={styles.price}>${item.price}</Text>
        <Text style={styles.calories}>{item.calories} calories</Text>
      </View>
      <TouchableOpacity
        style={inCart ? styles.removeFromCartButton : styles.addToCartButton}
        onPress={() => handleCart(item)}
        disabled={disabled}
      >
        <Text>{inCart ? "Remove from cart" : "Add to cart"}</Text>
      </TouchableOpacity>
    </View>
  );
};

export default ItemCard;

Refreshing

We can use the RefreshControl component to make our content refreshable. We can use the onRefresh prop to specify a function that will be called when the user refreshes the content. We can also use the refreshing prop to specify whether the content is currently refreshing. Let’s take a look at how we use this in our Market.tsx:

import React, { useState, useEffect, useCallback } from "react";
import { RefreshControl } from "react-native";

// We import a lot of stuff here, but we are omitting them for brevity

// We are also omitting a lot of code here for brevity, see the full code in the GitHub repo
const Market = () => {
  const [items, setItems] = useState<Item[]>([]);
  const [cart, setCart] = useState<Item[]>([]);
  const [refreshing, setRefreshing] = useState(false);

  const fetchItems = async () => {
    // fetch items
  };

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    fetchItems().then(() => setRefreshing(false));
  }, [items]);

  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === "ios" ? "padding" : "height"}
    >
      <SafeAreaView style={styles.container}>
        <Text style={styles.title}>Market</Text>
        <ScrollView
          style={styles.scrollView}
          refreshControl={
            <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
          }
        >
          {/* We render our items here, but we are omitting for brevity */}
        </ScrollView>
      </SafeAreaView>
    </KeyboardAvoidingView>
  );
};

export default Market;

There are a couple things to note in the code above. We set a state variable refreshing (boolean) to know if we are refreshing; we use the useCallback hook to create a function that will be called when the user refreshes the content. This function sets refreshing to true, calls fetchItems, and then sets refreshing back to false once the items have been fetched. We can then pass the state variable and the function to the refreshing prop and the onRefresh prop of the RefreshControl component, respectively. To be honest, I have no idea how RefreshControl works and I don’t care, I just know what it does and how to use it (Procedural Abstraction!).

Finally, we pass our RefreshControl component to the refreshControl prop of the ScrollView component. This will make our content refreshable by pulling down on the content.

Learn More

You can learn more about React Native here.

To learn more about TypeScript check out the TypeScript documentation.

To learn more about React, check out the React documentation.

Acknowledgements

This tutorial was built by Santiago Medina for VOID Tech. It was heavily based off of the React Native documentation. Find the links to the documentation in the “Learn More” section above.