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 React Native is
- what Expo is
- how to set up an React Native development environment
- how to create an app with React Native
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.
Closing the Gap between React Native and Expo
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:
- install JDK
- install Android Studio or Xcode
- install Watchman
- update the environment variable
- install the emulator
- install Node
- 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
- installing Node
- setting up Expo
- setting up emulators
- installing 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](https://github.com/coreybutler/nvm-windows/releases)
, 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.
- Go to Control Panel → System and Security → System. Once there, click the Advanced system settings menu on the left.
- That will open the system properties window. Click on the Environment Variables button:
Under the User variables section, highlight the Path variable and click the edit button.
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
andC:\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:
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 curlsudo 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 | bashsource ~/.profile
Keeping NVM Up To Date
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.gitcd watchmangit checkout v4.9.0./autogen.sh./configuremakesudo make install
You may encounter an issue which looks like the following:
CXX scm/watchman-Mercurial.oscm/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 herestruct FileInformation { ^~~~~~~~~~~~~~~cc1plus: all warnings being treated as errorsmake: *** [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/Sdkexport PATH=$PATH:$ANDROID_HOME/emulatorexport PATH=$PATH:$ANDROID_HOME/toolsexport PATH=$PATH:$ANDROID_HOME/tools/binexport 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/openjdkbrew 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 nvmecho "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/sdkexport 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:
Download and extract the
.tar.gz
file:tar -xzvf android-studio-ide-183.5522156-linux.tar.gz
Navigate to the extracted directory and go inside the
bin
directory.Execute
studio.sh
. This opens up the installer for Android Studio:./studio.sh
The Setup Wizard will first greet you with the following screen:
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:
Once it has installed everything, it will show the following. Just click on Finish to close the Setup Wizard:
The next step is to open Android Studio to configure the SDK platform and tools:
If You’ve Previously Installed Android Studio
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 Tools → SDK 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
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.0nvm alias default 11.2.0
Once installed, you can verify that it works by executing the following:
node --versionnpm --version
Here’s what the output will look like:
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.
RAM Alert
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
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:
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 Tools → AVD 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:
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:
Once installed, click Finish to close the current window then click on Next once you see this screen:
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 HelloWorldAppcd HelloWorldAppreact-native run-androidreact-native run-ios
Here’s what the app will look like by default:
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/toolsAndroid/sdk/tools/binAndroid/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_profileexport 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.xcodeprojxcrun: error: unable to find utility "instruments", not a developertool 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:
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:
Here’s a breakdown 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 theApp.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 useapp
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 theandroid
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:
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-androidreact-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:
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:
If you’re testing on a real device, shake it so the developer menu will show up:
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
orreact-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
).
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:
- Look for an existing package which implements what you want.
- Install it.
- 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.
- 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.jsimport 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 notsearchInput: '', // the currently input textname: '', // Pokemon namepic: '', // Pokemon image URLtypes: [], // Pokemon types arraydesc: '' // 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 statereturn (<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.wrapperView.containerView.headContainer View.textInputContainer TextInput View.buttonContainer ButtonView.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:
In contrast, (flexDirection: 'row'
) lays out items horizontally:
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
.
CSS Properties and React Native
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
:
<TextInputstyle={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:
<ButtononPress={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.jsimport 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 systemFlatList
: 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:
<Imagesource={{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:
<FlatListcolumnWrapperStyle={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 specifiedflexDirection: 'row'
.numColumns
: the maximum number of columns you want to render for each row on the list. In this case, we’ve specified2
, 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 akey
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-androidreact-native run-iosyarn 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.