Welcome to cross-platform app development. With React Native, your apps are platform-agnostic. They can be written for iOS, Android and mobile web with one code base. In this chapter, we will walk through the initial steps to create a React Native application from scratch.
- Use the Expo Command Line Interface (CLI)
- Structure a React Native application
- Fetch data from an API using axios
- Use Hooks to modify internal app state
- Create reusable components with props
- Style components using a custom Stylesheet
These docs are based on the React Native Getting Started Guide guide. Please note that this is under the "Expo CLI Quickstart" tab.
Let's begin with the Expo command line interface.
Expo is a workflow for mobile developers to build React Native apps. It is important to note that while this section uses Expo, Expo is not required.
With Expo, you have access to lots of built-in functionality, provided by the Expo SDK. Without Expo, you can build apps with Native Modules. TL;DR, if you are new to mobile app development, stick with Expo.
The two ways to use Expo tools are called the "managed" and "bare" workflows.
- Source: Expo Documentation
Curious about the Pros and Cons of using Expo?
Pros
- Lots of built-in third party libraries
- Easy to get started with (Expo CLI)
- No operating system requirements
- Great for rapid prototyping
- Excellent documentation
Cons
- No native modules (Java/Kotlin, Obj-C/Swift)
- Release management is taken care of by Expo
- Not always up to date with the latest version of React Native
- Eject is a one-way ticket
- Large initial app bundle size
In any case, we shall begin building our app by installing the Expo client, and running the init
command:
npm install -g expo-cli
expo init <app-name>
Having `permissions` issues running `npm install -g expo-cli`?
Please refer to this guide: npm docs
If you followed the npm docs, be sure to re-initialize your bash_profile
:
source ~/.bash_profile
expo init <app-name>
In this case, <app-name>
will be the name of your app. Please do not name it <app-name>
!
Keep in mind, our first app will be a writing prompts app, so try to give it a name that rolls off the tongue (or simply writing-app
).
Let's create a React Native app using the Expo CLI wizard:
- Select
blank
- this is a blank canvas app template.
? Choose a template: (Use arrow keys)
----- Managed workflow -----
❯ blank a minimal app as clean as an empty canvas
- Name your app. Again, do not name it
app-name
! Typing the full app name will edit it in yourpackage.json
file.
{
"expo": {
"name": "<app-name>",
"slug": "<app-slug>"
}
}
If you followed these steps successfully, you should have a new app with the following structure:
.
.expo-shared/
assets/
node_modules/
.gitignore
.watchmanconfig
App.js
app.json
babel.config.js
package.json
yarn-error.log
yarn.lock
Once set up, please cd
into the app's root directory and run yarn start
:
cd <app-name>
yarn start
Great! We are now running our first React Native application.
Our first app will allow users to read prompts and write stories on the go.
Ready to continue building our first view? Edit your App.js
to greet your new app:
export default function App() {
return (
<View style={styles.container}>
<Text>Hello World! Welcome to my app.</Text>
</View>
);
}
As you may have noticed, this looks just like Javascript and JSX (React).
React Native was built with React developers in mind. The difference is there are not any markup tags such as <div>
.
The React Native documentation illustrates these built-in components:
Team up with a partner and pick any component listed in the React Native documentation. With your partner, ask each other to read & explain the components you chose. Answer the following questions:
- What is the purpose of the component you chose?
- Is there a Web equivalent of the component?
(ex: A
<View>
in React Native version of a standard Web<div>
)
We will continue to build our first React Native app, a writing prompts app.
Let's create our first reusable component, called RoundedButton
.
For a challenge, try to create your own RoundedButton
component. The button should display text
inside of a pressable rounded button.
Save the following source code inside ./RoundedButton.js
.
Click here to display the RoundedButton source code.
import React from 'react';
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
export default function RoundedButton(props) {
const { text, icon, textColor, backgroundColor, onPress } = props;
const color = textColor || 'white';
return (
<TouchableOpacity
onPress={() => onPress && onPress()}
style={[
{ backgroundColor: backgroundColor || 'transparent' },
styles.wrapper
]}
>
<View style={styles.ButtonTextWrapper}>
{icon}
<Text style={[{ color }, styles.buttonText]}>{text}</Text>
</View>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
wrapper: {
padding: 15,
display: 'flex',
borderRadius: 40,
borderWidth: 1,
borderColor: 'white',
backgroundColor: 'white',
marginBottom: 15,
alignItems: 'center'
},
buttonText: {
fontSize: 16,
width: '100%',
textAlign: 'center'
},
ButtonTextWrapper: {
flexDirection: 'row',
justifyContent: 'flex-end'
}
});
The RoundedButton
component displays text
wrapped inside of a pressable rounded button. Let's bring it into our App.js
:
Add this import code snippet to the top of your App.js
:
import RoundedButton from './RoundedButton';
Enough imports! Let's display the rounded button inside App.js
.
Try to `import` and display your new rounded button. If you need help, click for the hint below.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import RoundedButton from './RoundedButton';
export default function App() {
return (
<View style={styles.container}>
<RoundedButton
text="Next"
textColor="#161616"
onPress={() =>
console.log("Pressed button!")
}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
},
});
Building apps in React Native allows us to use flexbox to position our elements.
Inside App.js
, try setting the justifyContent
property to flex-end
or flex-start
. You may notice the button move up and down.
Also inside App.js
, try experimenting with the RoundedButton
props, like text
and textColor
. Set the text
property to "Hello!"
and textColor
property to green
!
So far our app displays static information. In React Native, components may update their state to display dynamic data.
The writing prompts app is designed to display random prompts that inspire users to write.
For now, we will display a randomized background color.
- Inside
App.js
, we are going to modify the current source code. The new source code will provide state to our App using Hooks. - Hooks were introduced by the React team to use state and other React features without writing classes.
Prior to Hooks, class components would modify their state using lifecycle methods, such as the constructor
and componentDidMount
. With Hooks we can achieve the same internal state as class components, with less boilerplate code.
Inside of App.js
on line one, import the useState
method:
import React, { useState } from 'react';
Our App.js
will need to initialize it's state with a color
string. Let's declare our first hook, to add and modify this property:
// import statements
export default function App() {
const [color, setColor] = useState('#161616');
{/* Do not copy below... */}
return ()
}
Each time we press the RoundedButton
, let's set the background color to a random rgb()
value. Here's a function snippet to do just that...
For a challenge, try to create your own randomRgb
function.
Click here to display the `randomRgb` source code.
// Paste this snippet inside App.js, just above the stylesheet.
const randomRgb = () => {
const red = Math.floor(Math.random() * 256);
const green = Math.floor(Math.random() * 256);
const blue = Math.floor(Math.random() * 256);
return `rgb(${red}, ${green}, ${blue})`;
};
Now let's add the randomRgb
function to our RoundedButton
onPress method.
Click here to display the source code so far.
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import RoundedButton from './RoundedButton';
export default function App() {
const [color, setColor] = useState('#161616');
return (
<View style={[styles.container, { backgroundColor: color }]}>
<RoundedButton
text="Next"
textColor="#161616"
onPress={() => setColor(randomRgb())}
/>
</View>
);
}
const randomRgb = () => {
const red = Math.floor(Math.random() * 256);
const green = Math.floor(Math.random() * 256);
const blue = Math.floor(Math.random() * 256);
return `rgb(${red}, ${green}, ${blue})`;
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Refresh your app and click the "Next" button. If the app's background color changes, nicely done!! If not, debug your code and make sure it matches the above snippet.
The writing prompts app currently displays a static list of prompts. In order to make it a dynamic list, we created a server that sends random prompts.
First, we will install the axios
library for our network requests.
Heads Up! This lesson's code edits will only affect your React Native project.
Run the following command inside your React Native project.
yarn add axios
With Axios installed, we can query the localhost
server for random prompts.
Let's modify the React Native app code, with the following snippet inside App.js
:
// App.js
// top imports...
import axios from 'axios';
With Axios imported at the top of our App.js
, we can write an async
function that fetches a randomPrompt.
const randomPrompt = async () => {
try {
const response = await axios.get('https://prompts-express-api.herokuapp.com/random');
const prompt = response.data;
setPrompt(prompt.title);
} catch(err) {
console.log(err);
}
}
- Next, we will add a new hook method for the
prompt
string's state changes.
// App.js
const [color, setColor] = useState('#161616');
const [prompt, setPrompt] = useState('Hello!');
- Next, we will modify the
RoundedButton
'sonPress
method to utilize the new network request.
// App.js
...
<RoundedButton
text="Next"
textColor="#161616"
onPress={() => {
randomPrompt();
setColor(randomRgb());
}}
/>
...
Need a hint? Click here for the full `App.js` source code so far.
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import RoundedButton from './RoundedButton';
import axios from 'axios';
export default function App() {
const [color, setColor] = useState('#161616');
const [prompt, setPrompt] = useState('Hello!');
const randomPrompt = async () => {
try {
const response = await axios.get('https://prompts-express-api.herokuapp.com/random');
const prompt = response.data;
setPrompt(prompt.title);
} catch(err) {
console.log(err);
}
}
return (
<View style={[styles.container, { backgroundColor: color }]}>
<Text style={styles.prompt}>{prompt}</Text>
<RoundedButton
text="Next"
textColor="#161616"
onPress={() => {
randomPrompt();
setColor(randomRgb());
}}
/>
</View>
);
}
const randomRgb = () => {
const red = Math.floor(Math.random() * 256);
const green = Math.floor(Math.random() * 256);
const blue = Math.floor(Math.random() * 256);
return `rgb(${red}, ${green}, ${blue})`;
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
padding: 20
},
prompt: {
color: 'white',
fontSize: 22,
padding: 20,
textAlign: 'center'
}
});
Right now, we see a new prompt each time the user taps "Next". This is great, but our initial prompt is currently "Hello World!"
How can we remedy this using Hooks?
Given the new hooks API, we can call a method called useEffect
, which functions like the componentDidMount
and componentDidUpdate
lifecycle methods. Let's try it out!
At the top of App.js
, update your imports to reflect the following change:
import React, { useState, useEffect } from 'react';
After importing useEffect
, bring it into the fold.
export default function App() {
const [color, setColor] = useState('#161616');
const [prompt, setPrompt] = useState('Hello');
// useEffect runs on first render.
useEffect(() => {
randomPrompt();
}, '');
// ^^ Don't forget this empty string!
...
}
useEffect
will update our initial state to reflect the first random prompt.
If you like, add setColor(randomRgb())
to the useEffect
method. This will change the initial color as well.
With that said, we are ready to wrap up this lesson.
The app currently displays a unique prompt each time the user presses "Next".
Try adding a second button to Share the current prompt.
Today we made a react native app, with async requests to fetch random prompts.