Getting Started with React Native

With the ever-increasing popularity of smartphones, developers are looking into solutions for building mobile applications. For developers with a web background, frameworks such as Cordova and Ionic, React Native, NativeScript, and Flutter allow us to create mobile apps with languages we’re already familiar with: HTML, XML, CSS, and JavaScript.

In this guide, we’ll take a closer look at React Native. You’ll learn the absolute basics of getting started with it. Specifically, we’ll cover the following:

  • what is React Native
  • what is Expo
  • how to set up an React Native development environment
  • how to create an app with React Native

Want to learn React Native from the ground up? This article is an extract from our Premium library. Get an entire collection of React Native books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $9/month.

Prerequisites

This tutorial assumes that you’re coming from a web development background. The minimum requirement for you to be able to confidently follow this tutorial is to know HTML, CSS, and JavaScript. You should also know how to install software on your operating system and work with the command line. We’ll also be using some ES6 syntax, so it would help if you know basic ES6 syntax as well. Knowledge of React is helpful but not required.

What is React Native?

React Native is a framework for building apps that work on both Android and iOS. It allows you to create real native apps using JavaScript and React. This differs from frameworks like Cordova, where you use HTML to build the UI and it will just be displayed within the device’s integrated mobile browser (WebView). React Native has built in components which are compiled to native UI components, while your JavaScript code is executed through a virtual machine. This makes React Native more performant than Cordova.

Another advantage of React Native is its ability to access native device features. There are many plugins which you can use to access native device features, such as the camera and various device sensors. If you’re in need of a platform-specific feature that hasn’t been implemented yet, you can also build your own native modules — although that will require you to have considerable knowledge of the native platform you want to support (Java or Kotlin for Android, and Objective C or Swift for iOS).

If you’re coming here and you’re new to React, you might be wondering what it is. React is a JavaScript library for the Web for building user interfaces. If you’re familiar with MVC, it’s basically the View in MVC. React’s main purpose is to allow developers to build reusable UI components. Examples of these components include buttons, sliders, and cards. React Native took the idea of building reusable UI components and brought it into mobile app development.

What is Expo?

Before coming here, you might have heard of Expo. It’s even recommended in the official React Native docs, so you might be wondering what it is.

In simple terms, Expo allows you to build React Native apps without the initial headache that comes with setting up your development environment. It only requires you to have Node installed on your machine, and the Expo client app on your device or emulator.

But that’s just how Expo is initially sold. In reality, it’s much more than that. Expo is actually a platform that gives you access to tools, libraries and services for building Android and iOS apps faster with React Native. Expo comes with an SDK which includes most of the APIs you can ask for in a mobile app development platform:

Those are just few of the APIs you get access to out of the box if you start building React Native apps with Expo. Of course, these APIs are available to you as well via native modules if you develop your app using the standard React Native setup.

Plain React Native or Expo?

The real question is which one to pick up — React Native or Expo? There’s really no right or wrong answer. It all depends on the context and what your needs are at the moment. But I guess it’s safe to assume that you’re reading this tutorial because you want to quickly get started with React Native. So I’ll go ahead and recommend that you start out with Expo. It’s fast, simple, and easy to set up. You can dive right into tinkering with React Native code and get a feel of what it has to offer in just a couple of hours.

That said, I’ve still included the detailed setup instructions for standard React Native for those who want to do it the standard way. As you begin to grasp the different concepts, and as the need for different native features arises, you’ll actually find that Expo is kind of limiting. Yes, it has a lot of native features available, but not all the native modules that are available to standard React Native projects are supported.

Note: projects like unimodules are beginning to close the gap between standard React Native projects and Expo projects, as it allows developers to create native modules that works for both React Native and ExpoKit.

Setting Up the React Native Development Environment

In this section, we’ll set up the React Native development environment for all three major platforms: Windows, Linux, and macOS. We’ll also cover how to set up the Android and iOS simulators. Lastly, we’ll cover how to set up Expo. If you just want to quickly get started, I recommend that you scroll down to the “Setting up Expo” section.

Here are the general steps for setting up the environment. Be sure to match these general steps to the steps for each platform:

  1. install JDK
  2. install Android Studio or Xcode
  3. install Watchman
  4. update the environment variable
  5. install the emulator
  6. install Node
  7. install React Native CLI

You can skip to the section relevant to your operating system. Some steps — like setting up Android Studio — are basically the same for each operating system, so I’ve put them in their own section:

  • setting up on Windows
  • setting up on Linux
  • setting up on macOS
  • setting up Android Studio
  • install Node
  • setting up Expo
  • setting up emulators
  • install React Native CLI
  • troubleshooting common errors

Setting Up on Windows

This section will show you how to install and configure the software needed to create React Native apps on Windows. Windows 10 was used in testing for this.

Install Chocolatey

Windows doesn’t really come with its own package manager that we can use to install the needed tools. So the first thing we’ll do is install one called Chocolatey. You can install it by executing the following command on the command line or Windows Powershell:

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

We can now install the other tools we need by simply using Chocolatey.

Install Python

Python comes with the command line tools required by React Native:

choco install -y python 2

Install JDK

The JDK allows your computer to understand and run Java code. Be sure to install JDK version 8 as that’s the one required by React Native:

choco install jdk8

Install NVM

Node has an installer for Windows. It’s better to use NVM for Windows, as that will enable you to install multiple versions of Node so that you can test new versions, or use a different version depending on the project you’re currently working on. For that, you can use NVM for Windows. Download nvm-setup.zip, extract it and execute nvm-setup.exe to install it.

Install Watchman

Watchman optimizes the compilation time of your React Native app. It’s an optional install if you’re not working on a large project. You can find the install instructions on their website.

Update the Environment Variables

This is the final step in setting up React Native on Windows. This is where we update the environment variables so the operating system is aware of all the tools required by React Native. Follow these steps right before you install the React Native CLI.

  1. Go to Control PanelSystem and SecuritySystem. Once there, click the Advanced system settings menu on the left.

    Windows advanced system settings

  2. That will open the system properties window. Click on the Environment Variables button:

    System properties

  3. Under the User variables section, highlight the Path variable and click the edit button.

  4. On the edit screen, click the New button and enter the path to the Android SDK and platform tools. For me, it’s on C:\users\myUsername\AppData\Local\Android\Sdk and C:\users\myUsername\AppData\Local\Android\Sdk\platform-tools. Note that this is also where you add the path to the JDK if it isn’t already added:

    add path

Setting Up on Linux

This section will show you how to install and configure the tools required for developing React Native apps on Linux. I’ve specifically used Ubuntu 18.04 for testing things out, but you should be able to translate the commands to the Linux distribution you’re using.

Install Prerequisite Tools

The first step is to install the following tools. The first line installs the tools required by Node, and the second line is required by Watchman, which we’ll also install later:

sudo apt-get install build-essential libssl-dev curl
sudo apt-get install git autoconf automake python-dev

Install NVM

NVM allows us to install and use multiple versions of Node. You can install it with the following commands:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
source ~/.profile

Note: be sure to check out the latest version from the releases page to ensure the NVM version you’re installing is updated.

Install JDK

As seen earlier, React Native actually compiles the corresponding code to each of the platforms you wish to deploy to. The JDK enables your computer to understand and run Java code. The specific JDK version required by React Native is JDK version 8.

sudo apt-get install openjdk-8-jre

Install Watchman

Watchman is a tool for watching changes in the file system. It’s mainly used to speed up the compilation process. If you’ve enabled live preview on the app that you’re developing, the changes you make to the app will be reflected faster in the live preview. The following steps require Git to already be installed on your system:

git clone https://github.com/facebook/watchman.git
cd watchman
git checkout v4.9.0
./autogen.sh
./configure
make
sudo make install

You may encounter an issue which looks like the following:

 CXX      scm/watchman-Mercurial.o
scm/Mercurial.cpp: In constructor ‘watchman::Mercurial::infoCache::infoCache(std::__cxx11::string)’:
scm/Mercurial.cpp:16:40: error: ‘void* memset(void*, int, size_t)’ clearing an object of non-trivial type ‘struct watchman::FileInformation’; use assignment or value-initialization instead [-Werror=class-memaccess]
   memset(&dirstate, 0, sizeof(dirstate));
                                        ^
In file included from scm/Mercurial.h:10,
                 from scm/Mercurial.cpp:3:
./FileInformation.h:18:8: note: ‘struct watchman::FileInformation’ declared here
 struct FileInformation {
        ^~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
make: *** [Makefile:4446: scm/watchman-Mercurial.o] Error 1

Try the following command instead:

./configure --without-python  --without-pcre --enable-lenient

Update the Environment Variables

Updating the environment variables is necessary in order for the operating system to be aware of the tools you installed, so you can use them directly from the terminal. Note that this is the final step for setting up all the tools required by React Native. Follow this right before the step for installing the React Native CLI.

To update the environment variables, open your .bash_profile file:

sudo nano ~/.bash_profile

Add the following at the beginning then save the file:

export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

Note that the path above assumes that the Android SDK is installed on your user’s home directory:

echo $HOME

Setting up on macOS

Having a Mac allows you to develop both Android and iOS apps with React Native. In this section, I’ll show how you can set up the development environment for both Android and iOS.

Installing prerequisite tools

Since macOS already comes with Ruby and cURL by default, the only tool you need to install is Homebrew, a package manager for macOS:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

If you already have it installed, simply update it with the following:

brew update

For iOS, the following are required:

  • Latest version of Xcode: installs the tools required for compiling iOS apps
  • Watchman: for watching file changes
  • NVM: for installing Node

Install JDK

Install JDK version 8 for macOS, as that’s the one required by React Native:

brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk8

Install Watchman

Watchman speeds up the compilation process of your source code. Installing it is required for both Android and iOS:

brew install watchman

Install NVM

NVM allows you to install multiple versions of Node for testing purposes:

brew install nvm
echo "source $(brew  —  prefix nvm)/nvm.sh" >> .bash_profile

Update Environment Variables

After you’ve installed all the required tools, and right before you install the React Native CLI, it’s time to update the environment variables. This is an important step, because without doing it, the operating system won’t be aware of the tools required by React Native.

To update it, open your .bash_profile file:

sudo nano ~/.bash_profile

Then add the path to the Android SDK and platform tools:

export ANDROID_HOME=/Users/$USER/Library/Android/sdk
export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator

Setting up Android Studio

Android Studio is the easiest way to install the tools required for Android (Android SDK, Android Emulator) so it’s the method I usually recommend for beginners. You can download the installer for your specific platform here.

On Windows and macOS, it has a setup wizard which you can just run, clicking on Next until the install is complete. Be sure to select the .dmg file for macOS or the .exe file for Windows from here if the default download button downloads something different. You can use the following screenshots as a basis for your install.

If you’re on Linux, you need to follow these steps first before you can proceed to installing Android Studio:

  1. Download and extract the .tar.gz file:

    tar -xzvf android-studio-ide-183.5522156-linux.tar.gz
    
  2. Navigate to the extracted directory and go inside the bin directory.

  3. Execute studio.sh. This opens up the installer for Android Studio:

    ./studio.sh
    

The Setup Wizard will first greet you with the following screen:

Android studio setup

Just click on Next until you see the screen below. The Android SDK, and the latest Android API version, are checked by default. You can also check the Android Virtual Device if you want. This installs the default Android emulator for testing your apps. But I generally recommend using Genymotion instead, as it comes with better tools for testing out different device features:

Android Studio APIs

Once it has installed everything, it will show the following. Just click on Finish to close the Setup Wizard:

Android Studio finished installation

The next step is to open Android Studio to configure the SDK platform and tools:

SDK manager

Note: if you’ve previously installed Android Studio, you might have an existing project opened already. In that case, you can launch the SDK Manager from the ToolsSDK Manager top menu. Then check the Show Package Details at the bottom right. This way, you could choose only the sub-components instead of installing the whole thing.

Under the SDK Platforms tab, make sure that the following are checked:

  • Android 9.0 (Pie)
    • Android SDK Platform 28
    • Google APIs Intel x86 Atom_64 System Image or Intel x86 Atom_64 System Image

SDK manager 2

Under the SDK Tools tab, check the Show Package Details again and make sure that the following are checked:

  • Android SDK Build-Tools 29
    • 28.0.3
  • Android SDK Platform-Tools
  • Android SDK Tools
  • Android Support Repository
  • Google Repository

Check the following if you decided to install the Android Emulator:

  • Intel x86 Emulator Accelerator (HAXM installer)

That will optimize the emulator’s performance.

Install Node

Execute the following commands to install a specific version of Node and set it as the default:

nvm install 11.2.0
nvm alias default 11.2.0

Once installed, you can verify that it works by executing the following:

node --version
npm --version

Here’s what the output will look like:

node and npm version

Setting Up Expo

In this section, we’ll set up Expo, an alternative way of developing React Native apps. This section assumes that you already have Node and Watchman installed.

To set up Expo, all you have to do is install their command line tool via npm:

npm install -g expo-cli

That’s really all there is to it! The next step is to download the Expo client App for Android or iOS. Note that this is the only way you can run Expo apps while you’re still on development. Later on, you can build the standalone version.

From here, you can either proceed to the Hello World App section if you plan on running apps on your device, or the Setting up Emulators section if you want to run it on the Android Emulator or iOS Simulator.

Setting Up Emulators

Emulators allow you to test out apps you’re developing right from your development machine.

Note: emulators require your machine to have at least 4GB of RAM. Otherwise, they’ll really slow down your machine to the point where you get nothing done because of waiting for things to compile or load.

iOS Simulator

Xcode already comes pre-installed with iOS simulators, so all you have to do is launch it before you run your apps. To launch an iOS simulator from the command line, you first have to list the available simulators:

xcrun simctl list

iOS simulator list

Take note of the device UUID of the device you wish to run and substitute it with the value of UUID of the command below:

 open -a Simulator --args -CurrentDeviceUDID "UUID"

Genymotion

As mentioned earlier, I recommend Genymotion as the emulator for Android, as it has more device features that you can test out — for example, when testing out apps that make use of GPS. Genymotion allows you to select a specific place via a map interface:

Genymotion GPS

To install Genymotion, you first have to download and install VirtualBox. Once that’s done, sign up for a Genymotion account, log in, and download the installer. Windows and macOS come with a corresponding installer. But for Linux, you have to download the installer and make it executable:

chmod +x genymotion-<version>_<arch>.bin

After that, you can now run the installer:

./genymotion-<version>_<arch>.bin

Once it’s done installing, you should be able to search for it on your launcher.

Android Emulator

Even though Genymotion is the first thing I recommend, I believe that Android Emulator has its merits as well. For example, it boots up faster and it feels faster in general. I recommend it if your machine has lower specs or if you have no need for Genymotion’s additional features.

When you launch Android Studio, you can select AVD Manager from the configuration options (or ToolsAVD Manager if you currently have an open project).

Click on the Create Virtual Device button on the window that shows up. It will then ask you to choose the device you wish to install:

select virtual device

You can choose any that you want, but ideally, you want to have the ones which already have a Play Store included. It will be especially useful if your app integrates with Google Sign in or other apps, as it will allow you to install those apps with ease.

Next, it will ask you to download the version of Android you wish to install on the device. Simply select the latest version of Android that’s supported by React Native. At the time of writing this tutorial, it’s Android Pie. Note that this is also the same version that we installed for the Android SDK Platform earlier:

system image

Once installed, click Finish to close the current window then click on Next once you see this screen:

Android installed

Click on Finish on the next screen to create the emulator. It will then be listed on Android Virtual Device Manager. Click on the play button next to the emulator to launch it.

Install React Native CLI

The final step is to install the React Native CLI. This is the command line tool that allows you to bootstrap a new React Native project, link native dependencies, and run the app on a device or emulator:

npm install -g react-native-cli

Once it’s installed, you can try creating a new project and run it on your device or emulator:

react-native init HelloWorldApp
cd HelloWorldApp
react-native run-android
react-native run-ios

Here’s what the app will look like by default:

default React Native screen

At this point, you now have a fully functional React Native development environment set up.

Troubleshooting Common Errors

In this section, we’ll look at the most common errors you may encounter when trying to set up your environment.

Could Not Find tools.jar

You may get the following error:

Could not find tools.jar. Please check that /usr/lib/jvm/java-8-openjdk-amd64 contains a valid JDK installation

This means that the system doesn’t recognize your JDK installation. The solution is to re-install it:

sudo apt-get install openjdk-8-jdk

SDK Location Not Found

You may get the following error when you run your app:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring project ':app'.
> SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

This means that you haven’t properly added the path to all of the Android tools required by React Native. You can check it by executing the following:

echo $PATH

It should show the following path:

Android/sdk/tools
Android/sdk/tools/bin
Android/sdk/platform-tools

If not, then you have to edit either your .bashrc or .bash_profile file to add the missing path. The config below adds the path to the platform tools:

sudo nano ~/.bash_profile
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

Unable to Find Utility “instruments”

If you’re developing for iOS, you might encounter the following error when you try to run the app:

Found Xcode project TestProject.xcodeproj
xcrun: error: unable to find utility "instruments", not a developer
tool or in PATH

The problem is that Xcode command line tools aren’t installed yet. You can install them with the following command:

xcode-select --install

“Hello World” App

Now that your development environment is set up, you can start creating the obligatory “hello world” app. The app you’re going to create is a Pokemon search app. It will allow the user to type the name of a Pokemon and view its details.

Here’s what the final output will look like:

Pokemon Search App

You can find the source code on this GitHub repo.

Bootstrapping the App

On your terminal, execute the following command to create a new React Native project:

react-native init RNPokeSearch

For those of you who decided to use Expo instead, here’s the equivalent command for bootstrapping a new React Native project on Expo. Under Managed Workflow, select blank, enter “RNPokeSearch” for the project name, and install dependencies using Yarn:

expo init RNPokeSearch

Just like in the web environment, you can install libraries to easily implement different kinds of functionality in React Native. Once the project is created, we need to install a couple of dependencies: pokemon and axios. The former is used for verifying if the text entered in the search box is a real Pokemon name, while axios is used to make an HTTP request to the API that we’re using: PokeAPI:

yarn add pokemon axios

Note that the above command works on both standard React Native projects and Expo’s managed workflow, since they don’t have any native dependencies. If you’re using Expo’s managed workflow, you won’t be able to use packages that have native dependencies.

React Native Project Directory Structure

Before we proceed to coding, let’s first take a look at the directory structure of a standard React Native project:

Standard React Native directory structure

Here’s a break down of the most important files and folders that you need to remember:

  • App.js: the main project file. This is where you’ll start developing your app. Any changes you make to this file will be reflected on the screen.
  • index.js: the entry point file of any React Native project. This is responsible for registering the App.js file as the main component.
  • package.json: where the name and versions of the libraries you installed for this project are added.
  • node_modules: where the libraries you installed are stored. Note that this already contains a lot of folders before you installed the two libraries earlier. This is because React Native also has its own dependencies. The same is true for all the other libraries you install.
  • src: acts as the main folder which stores all the source code related to the app itself. Note that this is only a convention. The name of this folder can be anything. Some people use app as well.
  • android: where the Android-related code is. React Native isn’t a native language. That’s why we need this folder to bootstrap the Android app.
  • ios: where the iOS-related code is. This accomplishes the same thing as the android folder, but for iOS.

Don’t mind the rest of the folders and files for now, as we won’t be needing them when just getting started.

For Expo users, your project directory will look like this:

Expo project directory

As you can see, it’s pretty much the same. The only difference is that there’s no android and ios folders. This is because Expo takes care of running the app for you on both platforms. There’s also the addition of the assets folder. This is where app assets such as icons and splash screens are stored.

Running the App

At this point, you can now run the app. Be sure to connect your device to your computer, or open your Android emulator or iOS simulator before doing so:

react-native run-android
react-native run-ios

You already saw what the default screen looks like earlier.

If you’re on Expo, you can run the project with the following command. Be sure you’ve already installed the corresponding Expo client for your phone’s operating system before doing so:

yarn start

Once it’s running, it will display the QR code:

Expo yarn start

Open your Expo client app, and in the projects tab click on Scan QR Code. This will open the app on your Android or iOS device. If you have an emulator running, you can either press i to run it on the iOS simulator or a to run it on the Android emulator:

Expo client app

If you’re testing on a real device, shake it so the developer menu will show up:

React Native developer settings

Click on the following:

  • Enable Live Reload: automatically reloads your app when you hit save on any of its source code.
  • Start Remote JS Debugging: for debugging JavaScript errors on the browser. You can also use react-native log-android or react-native log-ios for this, but remote debugging has a nicer output, so it’s easier to inspect.

If you want, you can also set the debug server. This allows you to disconnect your mobile device from your computer while you develop the app. You can do that by selecting Dev Settings in the developer menu. Then under the Debugging section, select Debug server host & port for device. This opens up a prompt where you can enter your computer’s internal IP address and the port where Metro Bundler runs on (usually port 8081).

Metro Bundler port

Once you’ve set that, select Reload from the developer menu to commit the changes. At this point, you can now disconnect your device from the computer. Note that any time you install a package, you have to connect your device, quit Metro Bundler, and run the app again (using react-native run-android or react-native run-ios). That’s the only way for the changes to take effect.

Coding the App

Both standard React Native projects and Expo have built-in components which you can use to accomplish what you want. Simply dig through the documentation and you’ll find information on how to implement what you need. In most cases, you either need a specific UI component or an SDK which works with a service you plan on using. You can use Native Directory to look for those or just plain old Google. More often than not, here’s what your workflow is going to look like:

  1. Look for an existing package which implements what you want.
  2. Install it.
  3. Link it — only for native modules. If you’re on Expo, you don’t really need to do this because you can only install pure JavaScript libraries — although this might change soon because of the introduction of unimodules and bare workflow.
  4. Use it on your project.

Now that you’ve set up your environment and learned a bit about the workflow, we’re ready to start coding the app.

Start by replacing the contents of the App.js file with the following code:

import React from 'react';
import Main from './src/Main';

function App() {

  return <Main />

}

export default App;

The first line in the code above code imports React. You need to import this class any time you want create a component.

The second line is where we import a custom component called Main. We’ll create it later. For now, know that this is where we’ll put the majority of our code.

After that, we create the component by creating a new function. All this function does is return the Main component.

Lastly, we export the class so that it can be imported somewhere else. In this case, it’s actually imported from the index.js file.

Next, create the src/Main.js file and add the following:

// src/Main.js
import React, { Component } from 'react';
import { SafeAreaView, View, Text, TextInput, Button, Alert, StyleSheet, ActivityIndicator } from 'react-native';

The second line imports the components that are built into React Native. Here’s what each one does:

  • SafeAreaView: for rendering content within the safe area boundaries of a device. This automatically adds a padding that wraps its content so that it won’t be rendered on camera notches and sensor housing area of a device.
  • View: a fundamental UI building block. This is mainly used as a wrapper for all the other components so they’re structured in such a way that you can style them with ease. Think of it as the equivalent of <div>: if you want to use Flexbox, you have to use this component.
  • Text: for displaying text.
  • TextInput: the UI component for inputting text. This text can be plain text, email, password, or a number pad.
  • Button: for showing a platform-specific button. This component looks different based on the platform it runs on. If it’s Android, it uses Material Design. If it’s iOS, it uses Cupertino.
  • Alert: for showing alerts and prompts.
  • ActivityIndicator: for showing a loading animation indicator.
  • StyleSheet: for defining the component styles.

Next, import the libraries we installed earlier:

import axios from 'axios';
import pokemon from 'pokemon';

We’ll also be creating a custom Pokemon component later. This one is used for displaying Pokemon data:

import Pokemon from './components/Pokemon';

Because getting the required Pokemon data involves making two API requests, we have to set the API’s base URL as a constant:

const POKE_API_BASE_URL =  "https://pokeapi.co/api/v2";

Next, define the component class and initialize its state:

export default class Main extends Component {

  state = {
    isLoading: false, // decides whether to show the activity indicator or not
    searchInput: '', // the currently input text
    name: '', // Pokemon name
    pic: '', // Pokemon image URL
    types: [], // Pokemon types array
    desc: '' // Pokemon description
  }

  // next: add render() method
}

In the code above, we’re defining the main component of the app. You can do this by defining an ES6 class and having it extend React’s Component class. This is another way of defining a component in React. In the App.js file, we created a functional component. This time we’re creating a class-based component.

The main difference between the two is that functional components are used for presentation purposes only. Functional components have no need to keep their own state because all the data they require is just passed to them via props. On the other hand, class-based components maintain their own state and they’re usually the ones passing data to functional components.

If you want to learn more about the difference between functional and class-based components, read this tutorial: Functional vs Class-Components in React.

Going back to the code, we’re initializing the state inside our component. You define it as a plain JavaScript object. Any data that goes into the state should be responsible for changing what’s rendered by the component. In this case, we put in isLoading to control the visibility of the activity indicator and searchInput to keep track of the input value in the search box.

This is an important concept to remember. React Native’s built-in components, and even the custom components you create, accept properties that control the following:

  • what’s displayed on the screen (data source)
  • how they present it (structure)
  • what it looks like (styles)
  • what actions to perform when user interacts with it (functions)

We’ll go through those properties in more detail in the next section. For now, know that the value of those properties are usually updated through the state.

The rest of the state values are for the Pokemon data. It’s a good practice to set the initial value with the same type of data you’re expecting to store later on — as this serves as documentation as well.

Anything that’s not used for rendering or data flow can be defined as an instance variable, like so:

class Main extends Component {

    this.appTitle = "RNPokeSearch";

}

Alternatively, it can be defined as an outside-the-class variable, just like what we did with the POKE_API_BASE_URL earlier:

const POKE_API_BASE_URL = '';

class Main extends Component {

}

Structuring and Styling Components

Let’s return to the component class definition. When you extend React’s Component class, you have to define a render() method. This contains the code for returning the component’s UI and it’s made up of the React Native components we imported earlier.

Each component has its own set of props. These are basically attributes that you pass to the component to control a specific aspect of it. In the code below, most of them have the style prop, which is used to modify the styles of a component. You can pass any data type as a prop. For example, the onChangeText prop of the TextInput is a function, while the types prop in the Pokemon is an array of objects. Later on in the Pokemon component, you’ll see how the props will be used:

render() {
  const { name, pic, types, desc, searchInput, isLoading } = this.state; // extract the Pokemon data from the state
  return (
    <SafeAreaView style={styles.wrapper}>
      <View style={styles.container}>
        <View style={styles.headContainer}>
          <View style={styles.textInputContainer}>
            <TextInput
              style={styles.textInput}
              onChangeText={(searchInput) => this.setState({searchInput})}
              value={this.state.searchInput}
              placeholder={"Search Pokemon"}
            />
          </View>
          <View style={styles.buttonContainer}>
            <Button
              onPress={this.searchPokemon}
              title="Search"
              color="#0064e1"
            />
          </View>
        </View>

        <View style={styles.mainContainer}>
          {
            isLoading &&
            <ActivityIndicator size="large" color="#0064e1" />
          }

          {
            !isLoading &&
            <Pokemon
              name={name}
              pic={pic}
              types={types}
              desc={desc} />
          }
        </View>
      </View>
    </SafeAreaView>
  );
}

Breaking down the code above, we first extract the state data:

const { name, pic, types, desc, searchInput, isLoading } = this.state;

Next, we return the component’s UI, which follows this structure:

SafeAreaView.wrapper
  View.container
    View.headContainer
      View.textInputContainer
        TextInput
      View.buttonContainer
        Button
    View.mainContainer
      ActivityIndicator
      Pokemon

The above structure is optimized for using Flexbox. Go ahead and define the component styles at the bottom of the file:

const styles = StyleSheet.create({
  wrapper: {
    flex: 1
  },
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  headContainer: {
    flex: 1,
    flexDirection: 'row',
    marginTop: 100
  },
  textInputContainer: {
    flex: 2
  },
  buttonContainer: {
    flex: 1
  },
  mainContainer: {
    flex: 9
  },
  textInput: {
    height: 35,
    marginBottom: 10,
    borderColor: "#ccc",
    borderWidth: 1,
    backgroundColor: "#eaeaea",
    padding: 5
  }
});

In React Native, you define styles by using StyleSheet.create() and passing in the object that contains your styles. These style definitions are basically JavaScript objects, and they follow the same structure as your usual CSS styles:

element: {
  property: value
}

The wrapper and container is set to flex: 1, which means it will occupy the entirety of the available space because they have no siblings. React Native defaults to flexDirection: 'column', which means it will lay out the flex items vertically, like so:

flex direction column

In contrast, (flexDirection: 'row') lays out items horizontally:

flex direction row

It’s different for headContainer, because even though it’s set to flex: 1, it has mainContainer as its sibling. This means that headContainer and mainContainer will both share the same space. mainContainer is set to flex: 9 so it will occupy the majority of the available space (around 90%), while headContainer will only occupy about 10%.

Let’s move on to the contents of headContainer. It has textInputContainer and buttonContainer as its children. It’s set to flexDirection: 'row', so that its children will be laid out horizontally. The same principle applies when it comes to space sharing: textInputContainer occupies thw thirds of the available horizontal space, while buttonContainer only occupies one third.

The rest of the styles are pretty self explanatory when you have a CSS background. Just remember to omit - and set the following character to uppercase. For example, if you want to set background-color, the React Native equivalent is backgroundColor.

Note: not all CSS properties that are available on the Web are supported on React Native. For example, things like floats or table properties aren’t supported. You can find the list of supported CSS properties in the docs for View and Text components. Someone has also compiled a React Native Styling Cheat Sheet. There’s also a style section in the documentation for a specific React Native component that you want to use. For example, here are the style properties that you can use for the Image component.

Event Handling and Updating the State

Let’s now break down the code for the TextInput and Button components. In this section, we’ll talk about event handling, making HTTP requests, and updating the state in React Native.

Let’s start by examining the code for TextInput:

<TextInput
  style={styles.textInput}
  onChangeText={(searchInput) => this.setState({searchInput})}
  value={searchInput}
  placeholder={"Search Pokemon"}
/>

In the above code, we’re setting the function to execute when the user inputs something in the component. Handling events like these are similar to how it’s handled in the DOM: you simply pass the event name as a prop and set its value to the function you wish to execute. In this case, we’re simply inlining it because we’re just updating the state. The value input by the user is automatically passed as an argument to the function you supply so all you have to do is update the state with that value. Don’t forget to set the value of the TextInput to that of the state variable. Otherwise, the value input by the user won’t show as they type on it.

Next, we move on to the Button component. Here, we’re listening for the onPress event:

<Button
  onPress={this.searchPokemon}
  title="Search"
  color="#0064e1"
/>

Once pressed, the searchPokemon() function is executed. Add this function right below the render() method. This function uses the async/await pattern because performing an HTTP request is an asynchronous operation. You can also use Promises, but to keep our code concise, we’ll stick with async/await instead. If you’re not familiar with it, be sure to read this tutorial:

render() {
  // ...
}

searchPokemon = async () => {
  try {
    const pokemonID = pokemon.getId(this.state.searchInput); // check if the entered Pokemon name is valid

    this.setState({
      isLoading: true // show the loader while request is being performed
    });

    const { data: pokemonData } = await axios.get(`${POKE_API_BASE_URL}/pokemon/${pokemonID}`);
    const { data: pokemonSpecieData } = await axios.get(`${POKE_API_BASE_URL}/pokemon-species/${pokemonID}`);

    const { name, sprites, types } = pokemonData;
    const { flavor_text_entries } = pokemonSpecieData;

    this.setState({
      name,
      pic: sprites.front_default,
      types: this.getTypes(types),
      desc: this.getDescription(flavor_text_entries),
      isLoading: false // hide loader
    });

  } catch (err) {
    Alert.alert("Error", "Pokemon not found");
  }
}

Breaking down the code above, we first check if the entered Pokemon name is valid. If it’s valid, the National Pokedex ID (if you open the link, that’s the number on top of the Pokemon name) is returned and we supply it as a parameter for the HTTP request. The request is made using axios’ get() method, which corresponds to an HTTP GET request. Once the data is available, we extract what we need and update the state.

Here’s the getTypes() function. All it does is reassign the slot and type properties of the Pokemon types to id and name:

getTypes = (types) => {
  return types.map(({ slot, type }) => {
    return {
      "id": slot,
      "name": type.name
    }
  });
}

Here’s the getDescription() function. This finds the first English version of the flavor_text:

getDescription = (entries) => {
  return entries.find(item => item.language.name === 'en').flavor_text;
}

Pokemon Component

Earlier, we imported and used a component called Pokemon, but we haven’t really created it yet. Let’s go ahead and do so. Create a src/components/Pokemon.js file and add the following:

// src/components/Pokemon.js
import React from 'react';
import { View, Text, Image, FlatList, StyleSheet } from 'react-native';

const Pokemon = ({ name, pic, types, desc }) => {
  if (!name) {
    return null
  }

  return (
    <View style={styles.mainDetails}>
      <Image
        source={{uri: pic}}
        style={styles.image} resizeMode={"contain"} />
        <Text style={styles.mainText}>{name}</Text>

        <FlatList
          columnWrapperStyle={styles.types}
          data={types}
          numColumns={2}
          keyExtractor={(item) => item.id.toString()}
          renderItem={({item}) => {
            return (
              <View style={[styles[item.name], styles.type]}>
                <Text style={styles.typeText}>{item.name}</Text>
              </View>
            )
          }}
        />

        <View style={styles.description}>
          <Text>{desc}</Text>
        </View>
    </View>
  );
}

//
const styles = StyleSheet.create({
  mainDetails: {
    padding: 30,
    alignItems: 'center'
  },
  image: {
    width: 100,
    height: 100
  },
  mainText: {
    fontSize: 25,
    fontWeight: 'bold',
    textAlign: 'center'
  },
  description: {
    marginTop: 20
  },
  types: {
    flexDirection: 'row',
    marginTop: 20
  },
  type: {
    padding: 5,
    width: 100,
    alignItems: 'center'
  },
  typeText: {
    color: '#fff',
  },
  normal: {
    backgroundColor: '#8a8a59'
  },
  fire: {
    backgroundColor: '#f08030'
  },
  water: {
    backgroundColor: '#6890f0'
  },
  electric: {
    backgroundColor: '#f8d030'
  },
  grass: {
    backgroundColor: '#78c850'
  },
  ice: {
    backgroundColor: '#98d8d8'
  },
  fighting: {
    backgroundColor: '#c03028'
  },
  poison: {
    backgroundColor: '#a040a0'
  },
  ground: {
    backgroundColor: '#e0c068'
  },
  flying: {
    backgroundColor: '#a890f0'
  },
  psychic: {
    backgroundColor: '#f85888'
  },
  bug: {
    backgroundColor: '#a8b820'
  },
  rock: {
    backgroundColor: '#b8a038'
  },
  ghost: {
    backgroundColor: '#705898'
  },
  dragon: {
    backgroundColor: '#7038f8'
  },
  dark: {
    backgroundColor: '#705848'
  },
  steel: {
    backgroundColor: '#b8b8d0'
  },
  fairy: {
    backgroundColor: '#e898e8'
  }
});

export default Pokemon;

In the code above, we first checked if the name has a falsy value. If it has, we simply return null as there’s nothing to render.

We’re also using two new, built-in React Native components:

  • Image: used for displaying images from the Internet or from the file system
  • FlatList: used for displaying lists

As we saw earlier, we’re passing in the Pokemon data as prop for this component. We can extract those props the same way we extract individual properties from an object:

const Pokemon = ({ name, pic, types, desc }) => {
  // ..  
}

The Image component requires the source to be passed to it. The source can either be an image from the file system, or, in this case, an image from the Internet. The former requires the image to be included using require(), while the latter requires the image URL to be used as the value of the uri property of the object you pass to it.

resizeMode allows you to control how the image will be resized based on its container. We used contain, which means it will resize the image so that it fits within its container while still maintaining its aspect ratio. Note that the container is the Image component itself. We’ve set its width and height to 100, so the image will be resized to those dimensions. If the original image has a wider width than its height, a width of 100 will be used, while the height will adjust accordingly to maintain the aspect ratio. If the original image dimension is smaller, it will simply maintain its original size:

<Image
  source={{uri: pic}}
  style={styles.image}
  resizeMode={"contain"} />

Next is the FlatList component. It’s used for rendering a list of items. In this case, we’re using it to render the types of the Pokemon. This requires the data, which is an array containing the items you want to render, and the renderItem, which is the function responsible for rendering each item on the list. The item in the current iteration can be accessed the same way props are accessed in a functional component:

<FlatList
  columnWrapperStyle={styles.types}
  data={types}
  numColumns={2}
  keyExtractor={(item) => item.id.toString()}
  renderItem={({item}) => {
    return (
      <View style={[styles[item.name], styles.type]}>
        <Text style={styles.typeText}>{item.name}</Text>
      </View>
    )
  }}
/>

In the code above, we also supplied the following props:

  • columnWrapperStyle: used for specifying the styles for each column. In this case, we want to render each list item inline, so we’ve specified flexDirection: 'row'.
  • numColumns: the maximum number of columns you want to render for each row on the list. In this case, we’ve specified 2, because a Pokemon can only have two types at most.
  • keyExtractor: the function to use for extracting the keys for each item. You can actually omit this one if you pass a key prop to the outer-most component of each of the list items.

At this point, you can now test the app on your device or emulator:

react-native run-android
react-native run-ios
yarn start

Conclusion and Next Steps

That’s it! In this tutorial, you’ve learned how to set up the React Native development environment using both the standard procedure and Expo. You also learned how to create your very first React Native app.

To learn more, check out these resources:

You can find the source code used in this tutorial on this GitHub repo.