In this chapter, we will cover how to set up your local development environment for working with React Native. Then we will go through the basics of creating a simple application that you can deploy to your own iOS or Android device.
Setting up your development environment will enable you to follow along with the examples in the book and write your own applications.
There are two general approaches to setting up a development environment for React Native. The first, a tool called Create React Native App, gives you a quicker, easier installation but supports only pure-JavaScript applications. The second, more traditional approach involves fully installing React Native and all of its dependencies. Think of Create React Native App as a shortcut for easier testing and prototyping.
Information on migrating from Create React Native App to a full React Native project can be found in Appendix C.
Which approach should you take? I recommend that beginners use Create React Native App for educational purposes and quick prototyping.
Eventually, if you’re working on a React Native app professionally or writing a hybrid app that uses both JavaScript and native Java, Objective-C, or Swift code, you’ll want to install the full React Native developer setup.
Both approaches are described next. The example code in subsequent chapters will typically work with either approach; when something is incompatible with Create React Native App and requires a full React Native project, it will be noted.
Create React Native App is a command-line tool that allows you to quickly create and run React Native applications without needing to install Xcode or Android Studio.
If you want to get up and running quickly, then Create React Native App is the right choice.
Create React Native App is a great tool, but as mentioned earlier it supports only pure-JavaScript applications. Later in this book, we’ll discuss ways of integrating React Native applications with native code written in Java or Objective-C. Don’t worry: if you begin with Create React Native App, you can still “eject” into a full React Native project.
Let’s start by installing the create-react-native-app package from npm. React Native uses npm, the Node.js package manager, to manage dependencies. The npm registry includes packages for all sorts of JavaScript projects, not just Node.
npminstall-gcreate-react-native-app
To create a new project with Create React Native App, run the following command:
create-react-native-appfirst-project
This will install some JavaScript dependencies, as well as create the boilerplate for your application. Your project directory will look something like this:
. ├── App.js ├── App.test.js ├── README.md ├── app.json ├── node_modules ├── package.json └── yarn.lock
This structure looks like what you might expect from a simple JavaScript project. There is a package.json file, which contains metadata about the project and its dependencies. The README.md file includes information for running the project. App.test.js includes a simple test file. The code for your application is located in App.js. To modify this project and build out your own application, you would begin with App.js.
We will cover what this code is doing in more detail once we start building our weather application in “Building a Weather App”.
Great—now your application is ready for testing. To launch your application, run:
cdfirst-projectnpmstart
You should see the screen shown in Figure 3-1.
In order to view your application, you’ll need the Expo app for iOS or Android. Once you have it installed, point your phone’s camera at the QR code, and your React Native app will load. Note that your phone and computer will need to be on the same network, and able to communicate with each other.
Congrats! You’ve created your first React Native app, compiled it, and gotten it running on a real device.
In the next section, we’ll cover how to do a full, traditional installation of React Native. You can skip to “Exploring the Sample Code” instead if you’d like to get started programming.
Instructions for installing React Native and all of its dependencies can be found in the official React Native documentation.
You can use Windows, macOS, or Linux to develop applications with React Native. However, macOS is required to develop iOS applications. Linux and Windows users can still use React Native to write Android applications.
Because the setup instructions vary by platform and React Native version, we won’t go into them in detail here, but you’ll need to set up the following:
node.js
React Native
iOS development environment (Xcode)
Android development environment (JDK, Android SDK, Android Studio)
If you don’t want to install developer tools for both iOS and Android, that’s fine—just make sure that you have at least one of them set up.
You can use the React Native command-line tools to create a new application. Run the following command to install the command-line tools:
npminstall-greact-native-cli
Now we can generate a fresh project with all of the React Native, iOS, and Android boilerplate we’ll need by running:
react-nativeinitFirstProject
The resulting directory structure should look similar to the following:
. ├── __tests__ ├── android ├── app.json ├── index.android.js ├── index.ios.js ├── ios ├── node_modules ├── package.json └── yarn.lock
The ios/ and android/ directories contain boilerplate relevant to those platforms. Your React code is located in the index.ios.js and android.ios.js files, which are the respective entry points for your React application. Dependencies installed via npm can, as usual, be found in the node_modules/ folder.
To run your app on iOS, start by navigating into your newly created project’s directory. Then you can run your React Native application like so:
cd FirstProject react-native run-ios
Alternatively, you can open your application in Xcode and launch the iOS simulator from there:
open ios/FirstProject.xcodeproj
You can also use Xcode to upload your application to a real device for testing. In order to do this, you will need a free Apple ID so that you can configure code signing.
To configure code signing, open your project in the Xcode Project Navigator and select your main target, which should have the same name as your project. Next, select the General tab. Under the Signing menu, select your Apple developer account from the Team drop-down (see Figure 3-2). You will then need to repeat this step for the Tests target.
The first time you attempt to run your application on any particular device, Xcode will prompt you to sign into your Apple account and register your device for development.
For more details on how to run your app on a real iOS device, check out Apple’s official documentation.
Note that your iOS device and your computer must be on the same network in order for your application to run.
In order to run your application on Android, you need a fully functioning Android developer setup, including Android Studio and the Android SDK. See the Getting Started documentation for a list of Android dependencies.
To launch your React Native platform on Android, run:
react-native run-android
You can also open your application in Android Studio and compile and run it from there.
You can either run your application in the Android emulator or on a physical device connected via USB. In order to run on a physical device, you will need to enable USB debugging in your device’s Developer Options. More detailed instructions are available in the Android Studio documentation.
Now that you have launched and deployed the default application, let’s figure out how it works. In this section, we will dig into the source code of the default application and explore the structure of a React Native project.
If you are using Create React Native App, open the file App.js (see Example 3-1). If you are using a full React Native project, open up index.ios.js or index.android.js (see Example 3-2).
importReactfrom"react";import{StyleSheet,Text,View}from"react-native";exportdefaultclassAppextendsReact.Component{render(){return(<Viewstyle={styles.container}><Text>Hello,world!</Text></View>);}}conststyles=StyleSheet.create({container:{flex:1,backgroundColor:"#fff",alignItems:"center",justifyContent:"center"}});
importReact,{Component}from'react';import{AppRegistry,StyleSheet,Text,View}from'react-native';exportdefaultclassFirstProjectextendsComponent{render(){return(<Viewstyle={styles.container}><Textstyle={styles.welcome}>WelcometoReactNative!</Text><Textstyle={styles.instructions}>Togetstarted,editindex.ios.js</Text><Textstyle={styles.instructions}>PressCmd+Rtoreload,{'\n'}Cmd+Dorshakefordevmenu</Text></View>);}}conststyles=StyleSheet.create({container:{flex:1,justifyContent:'center',alignItems:'center',backgroundColor:'#F5FCFF',},welcome:{fontSize:20,textAlign:'center',margin:10,},instructions:{textAlign:'center',color:'#333333',marginBottom:5,},});AppRegistry.registerComponent('FirstProject',()=>FirstProject);
Either way, let’s talk about what’s going on here.
As you can see in Example 3-3, the import statements used are a bit different than what you might expect from a web-based React project.
importReact,{Component}from"react";import{StyleSheet,Text,View}from"react-native";
There’s some interesting syntax going on here. React is imported as usual, but what is happening on the next line?
One quirk of working with React Native is that you need to explicitly import every Native-provided module you work with. Elements like <div> don’t simply exist; instead, you need to explicitly import components such as <View> and <Text>. Library functions such as Stylesheet and AppRegistry also must be explicitly imported with this syntax. Once we start building our own applications, we will explore the other React Native functions that you may need to import.
If the syntax is unfamiliar to you, check out Example A-4 in Appendix A for an explanation of destructuring in ES6.
Next, let’s look at the component class in Example 3-4. This should all look comfortably familiar because it’s an ordinary React component. The main difference is its use of <Text> and <View> components instead of <div> and <span>, and the use of style objects.
exportdefaultclassFirstProjectextendsComponent{render(){return(<Viewstyle={styles.container}><Textstyle={styles.welcome}>WelcometoReactNative!</Text><Textstyle={styles.instructions}>Togetstarted,editindex.ios.js</Text><Textstyle={styles.instructions}>PressCmd+Rtoreload,{'\n'}Cmd+Dorshakefordevmenu</Text></View>);}}conststyles=StyleSheet.create({container:{flex:1,justifyContent:'center',alignItems:'center',backgroundColor:'#F5FCFF',},welcome:{fontSize:20,textAlign:'center',margin:10,},instructions:{textAlign:'center',color:'#333333',marginBottom:5,},});
As I mentioned earlier, all styling in React Native is done with style objects rather than stylesheets. The standard method of handling styling is by utilizing the StyleSheet library. You can see how the style objects are defined toward the bottom of the file. Note that only <Text> components can take text-specific styles like fontSize, and that all layout logic is handled by flexbox. We will discuss how to build layouts with flexbox at greater length in Chapter 5.
The sample application is a good demonstration of the basic functions you will need to create React Native applications. It mounts a React component for rendering and demonstrates the basics of styling and rendering in React Native. It also gives us a simple way to test our development setup and try deploying to a real device. However, it’s still a very basic application with no user interaction. So now let’s try building a more full-featured application.
In this section, we will be building off of the sample application to create a weather app. This will give us a chance to explore how to utilize and combine stylesheets, flexbox, network communication, user input, and images into a useful app we can then deploy to an Android or iOS device.
This section may feel like a bit of a blur, as it will be giving you an overview of these features rather than deep explanations of them. The weather app will serve as a useful reference in future sections as we discuss these features in more detail, however, so don’t worry if it feels like we’re moving quickly!
As shown in Figure 3-3, the final application includes a text field where users can input a zip code. It will then fetch data from the OpenWeatherMap API and display the current weather.
The first thing we’ll do is replace the default code from our sample app. Move the initial component out into its own file, WeatherProject.js.
If you created a full React Native project, you will need to replace the contents of index.ios.js and index.android.js, as shown in Example 3-5.
import{AppRegistry}from"react-native";importWeatherProjectfrom"./WeatherProject";AppRegistry.registerComponent("WeatherProject",()=>WeatherProject);
Similarly, if you created a React Native project with Create React Native App, you will need to replace the contents of App.js, as shown in Example 3-6.
importWeatherProjectfrom"./WeatherProject";exportdefaultWeatherProject;
We want the user to be able to enter a zip code and get the forecast for that area, so we need to add a text field for user input. We can start by adding zip code information to our component’s initial state (see Example 3-7).
constructor(props){super(props);this.state={zip:""};}
If you’re accustomed to using React.createClass() to create components instead of JavaScript classes, this may seem odd. When creating component classes, we set the initial state values for React components by mutating the this.state variable in the constructor method. If you need a review of the React component lifecycle, see the React docs.
Next, we should also change one of the <Text> components to display this.state.zip, as shown in Example 3-8.
<Textstyle={styles.welcome}>Youinput{this.state.zip}.</Text>
With that out of the way, let’s add a <TextInput> component (see Example 3-9). This is a basic component that allows the user to enter text.
<TextInputstyle={styles.input}onSubmitEditing={this._handleTextChange}/>
The <TextInput> component is documented in the React Native docs, along with its properties. You can also pass the <TextInput> additional callbacks in order to listen to other events, such as onChange or onFocus, but we do not need them at the moment.
Note that we’ve added a simple style to the <TextInput>. Add the input style to your stylesheet like so:
conststyles=StyleSheet.create({...input:{fontSize:20,borderWidth:2,height:40}...});
The callback we passed as the onSubmitEditing prop should be added as a function on the component, as shown in Example 3-10.
_handleTextChange=event=>{this.setState({zip:event.nativeEvent.text})}
By using fat-arrow syntax, we ensure that our callback is properly bound to the component instance. React autobinds lifecycle methods such as render, but for other methods we need to pay attention to binding. Fat-arrow functions are covered in Example A-8.
You will also need to update your import statements, as shown in Example 3-11.
import{...TextInput...}from"react-native;
Now try running your application using either the iOS simulator or the Android emulator. It won’t be pretty, but you should be able to successfully submit a zip code and see it reflected in the <Text> component.
If we wanted, we could add some simple input validation here to ensure that the user typed in a five-digit number, but we will skip that for now.
Example 3-12 shows the full code for the WeatherProject.js component thus far.
importReact,{Component}from"react";import{StyleSheet,Text,View,TextInput}from"react-native";classWeatherProjectextendsComponent{constructor(props){super(props);this.state={zip:""};}_handleTextChange=event=>{this.setState({zip:event.nativeEvent.text});};render(){return(<Viewstyle={styles.container}><Textstyle={styles.welcome}>Youinput{this.state.zip}.</Text><TextInputstyle={styles.input}onSubmitEditing={this._handleTextChange}/></View>);}}conststyles=StyleSheet.create({container:{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"#F5FCFF"},welcome:{fontSize:20,textAlign:"center",margin:10},input:{fontSize:20,borderWidth:2,padding:2,height:40,width:100,textAlign:"center"}});exportdefaultWeatherProject;
Now let’s work on displaying the forecast for that zip code. We will start by adding some mock data to our initial state value in WeatherProject.js:
constructor(props){super(props);this.state={zip:"",forecast:null};}
For sanity’s sake, let’s also pull the forecast rendering into its own component. Make a new file called Forecast.js (see Example 3-13).
importReact,{Component}from"react";import{StyleSheet,Text,View}from"react-native";classForecastextendsComponent{render(){return(<Viewstyle={styles.container}><Textstyle={styles.bigText}>{this.props.main}</Text><Textstyle={styles.mainText}>Currentconditions:{this.props.description}</Text><Textstyle={styles.bigText}>{this.props.temp}°F</Text></View>);}}conststyles=StyleSheet.create({container:{height:130},bigText:{flex:2,fontSize:20,textAlign:"center",margin:10,color:"#FFFFFF"},mainText:{flex:1,fontSize:16,textAlign:"center",color:"#FFFFFF"}});exportdefaultForecast;
The <Forecast> component just renders some <Text> based on its props. We’ve also included some simple styles at the bottom of the file to control things like text color.
Import the <Forecast> component and then add it to your app’s render method, passing it props based on this.state.forecast (see Example 3-14). We’ll address issues with layout and styling later. You can see how the <Forecast> component appears in the resulting application in Figure 3-4.
importReact,{Component}from"react";import{StyleSheet,Text,View,TextInput}from"react-native";importForecastfrom"./Forecast";classWeatherProjectextendsComponent{constructor(props){super(props);this.state={zip:"",forecast:null};}_handleTextChange=event=>{this.setState({zip:event.nativeEvent.text});};render(){letcontent=null;if(this.state.forecast!==null){content=(<Forecastmain={this.state.forecast.main}description={this.state.forecast.description}temp={this.state.forecast.temp}/>);}return(<Viewstyle={styles.container}><Textstyle={styles.welcome}>Youinput{this.state.zip}.</Text>{content}<TextInputstyle={styles.input}onSubmitEditing={this._handleTextChange}/></View>);}}conststyles=StyleSheet.create({container:{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"#F5FCFF"},welcome:{fontSize:20,textAlign:"center",margin:10},input:{fontSize:20,borderWidth:2,padding:2,height:40,width:100,textAlign:"center"}});exportdefaultWeatherProject;
Because we still don’t have a forecast to render, nothing should change visually yet.
Next, let’s explore using the networking APIs available in React Native. You won’t be using jQuery to send AJAX requests from mobile devices. Instead, React Native implements the Fetch API. The Promise-based syntax, shown in Example 3-15, is fairly simple.
fetch('http://www.somesite.com').then((response)=>response.text()).then((responseText)=>{console.log(responseText);});
If you’re not accustomed to working with Promises, see “Working with Promises”.
We will be using the OpenWeatherMap API, which provides us with a simple endpoint that returns the current weather for a given zip code. A small library for this API is provided in open_weather_map.js, shown in Example 3-16.
constWEATHER_API_KEY="bbeb34ebf60ad50f7893e7440a1e2b0b";constAPI_STEM="http://api.openweathermap.org/data/2.5/weather?";functionzipUrl(zip){return`${API_STEM}q=${zip}&units=imperial&APPID=${WEATHER_API_KEY}`;}functionfetchForecast(zip){returnfetch(zipUrl(zip)).then(response=>response.json()).then(responseJSON=>{return{main:responseJSON.weather[0].main,description:responseJSON.weather[0].description,temp:responseJSON.main.temp};}).catch(error=>{console.error(error);});}exportdefault{fetchForecast:fetchForecast};
Let’s import it now:
importOpenWeatherMapfrom"./open_weather_map";
To integrate it into our application, we can change the callback on the <TextInput> component to query the OpenWeatherMap API, as shown in Example 3-17.
_handleTextChange=event=>{letzip=event.nativeEvent.text;OpenWeatherMap.fetchForecast(zip).then(forecast=>{console.log(forecast);this.setState({forecast:forecast});});};
Logging the forecast here is a nice sanity check for us; for more detailed information on how to view the console output, see “Debugging with console.log”.
Finally, we also need to update the styling for our container so that we can see the forecast text render:
container:{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"#666666"}
Now, when you enter a zip code, you should actually see a forecast render (Figure 3-4).
The updated code for WeatherProject.js is shown in Example 3-18.
importReact,{Component}from"react";import{StyleSheet,Text,View,TextInput}from"react-native";importOpenWeatherMapfrom"./open_weather_map";importForecastfrom"./Forecast";classWeatherProjectextendsComponent{constructor(props){super(props);this.state={zip:"",forecast:null};}_handleTextChange=event=>{letzip=event.nativeEvent.text;OpenWeatherMap.fetchForecast(zip).then(forecast=>{this.setState({forecast:forecast});});};render(){letcontent=null;if(this.state.forecast!==null){content=(<Forecastmain={this.state.forecast.main}description={this.state.forecast.description}temp={this.state.forecast.temp}/>);}return(<Viewstyle={styles.container}><Textstyle={styles.welcome}>Youinput{this.state.zip}.</Text>{content}<TextInputstyle={styles.input}onSubmitEditing={this._handleTextChange}/></View>);}}conststyles=StyleSheet.create({container:{flex:1,justifyContent:"center",alignItems:"center",backgroundColor:"#666666"},welcome:{fontSize:20,textAlign:"center",margin:10},input:{fontSize:20,borderWidth:2,padding:2,height:40,width:100,textAlign:"center"}});exportdefaultWeatherProject;
Plain background colors are boring. Let’s display a background image to go along with our forecast.
Image assets are managed much like any other code asset: you can include them with a require call. We are going to use a file called flowers.png as our background image. It can be required like so:
<Imagesource={require('./flowers.png')}/>
The image file is available in the GitHub repository.
Just like JavaScript assets, if you have a flowers.ios.png and a flowers.android.png file, the React Native packager will load the appropriate image based on the platform. Likewise, you can use the @2x and @3x suffixes to provide different image files for different screen densities. So, hypothetically, we could structure our project directory like so:
. ├── flowers.png ├── flowers@2x.png ├── flowers@3x.png ...
To add a background image to a <View>, we don’t set a background property on a <div> like we do on the web. Instead, we use an <Image> component as a container:
<Imagesource={require('./flowers.png')}resizeMode='cover'style={styles.backdrop}>// Your content here</Image>
The <Image> component expects a source prop, which we get by using require.
Don’t forget to style it with flexDirection so that its children render as we’d like them to:
backdrop:{flex:1,flexDirection:'column'}
Now let’s give the <Image> some children. Update the render method of the <WeatherProject> component to return the following:
<Viewstyle={styles.container}><Imagesource={require("./flowers.png")}resizeMode="cover"style={styles.backdrop}><Viewstyle={styles.overlay}><Viewstyle={styles.row}><Textstyle={styles.mainText}>Currentweatherfor</Text><Viewstyle={styles.zipContainer}><TextInputstyle={[styles.zipCode,styles.mainText]}onSubmitEditing={event=>this._handleTextChange(event)}/></View></View>{content}</View></Image></View>
You’ll notice that I’m using some additional styles that we haven’t discussed yet, such as row, overlay, zipContainer, and zipCode. You can skip ahead to Example 3-19 to see the full stylesheet.
For the final version of the application, I’ve reorganized the <WeatherProject> component’s render function and tweaked the styles. The main change is to the layout logic, which is diagrammed in Figure 3-5.
Okay, ready to see it all in one place? Example 3-19 shows the finished code for the <WeatherProject> component in full, including the stylesheets. The <Forecast> component will be the same as shown previously in Example 3-13.
importReact,{Component}from"react";import{StyleSheet,Text,View,TextInput,Image}from"react-native";importForecastfrom"./Forecast";importOpenWeatherMapfrom"./open_weather_map";classWeatherProjectextendsComponent{constructor(props){super(props);this.state={zip:"",forecast:null};}_handleTextChange=event=>{letzip=event.nativeEvent.text;OpenWeatherMap.fetchForecast(zip).then(forecast=>{this.setState({forecast:forecast});});};render(){letcontent=null;if(this.state.forecast!==null){content=(<Forecastmain={this.state.forecast.main}description={this.state.forecast.description}temp={this.state.forecast.temp}/>);}return(<Viewstyle={styles.container}><Imagesource={require("./flowers.png")}resizeMode="cover"style={styles.backdrop}><Viewstyle={styles.overlay}><Viewstyle={styles.row}><Textstyle={styles.mainText}>Currentweatherfor</Text><Viewstyle={styles.zipContainer}><TextInputstyle={[styles.zipCode,styles.mainText]}onSubmitEditing={this._handleTextChange}underlineColorAndroid="transparent"/></View></View>{content}</View></Image></View>);}}constbaseFontSize=16;conststyles=StyleSheet.create({container:{flex:1,alignItems:"center",paddingTop:30},backdrop:{flex:1,flexDirection:"column"},overlay:{paddingTop:5,backgroundColor:"#000000",opacity:0.5,flexDirection:"column",alignItems:"center"},row:{flexDirection:"row",flexWrap:"nowrap",alignItems:"flex-start",padding:30},zipContainer:{height:baseFontSize+10,borderBottomColor:"#DDDDDD",borderBottomWidth:1,marginLeft:5,marginTop:3},zipCode:{flex:1,flexBasis:1,width:50,height:baseFontSize},mainText:{fontSize:baseFontSize,color:"#FFFFFF"}});exportdefaultWeatherProject;
Now that we’re done, try launching the application. It should work on both Android and iOS, in an emulator or on your physical device. What would you like to change or improve?
You can view the completed application in the GitHub repository.
For our first real application, we’ve already covered a lot of ground. We introduced a new UI component, <TextInput>, and learned how to use it to get information from the user. We demonstrated how to implement basic styling in React Native, as well as how to use images and include assets in our application. Finally, we learned how to use the React Native networking API to request data from external web sources. Not bad for a first application!
Hopefully, this chapter has demonstrated how quickly you can build React Native applications with useful features that feel at home on a mobile device.
If you want to extend your application further, here are some things to try:
Add more images and change them based on the forecast
Add validation to the zip code field
Switch to using a more appropriate keypad for the zip code input
Display the five-day weather forecast
Once we cover more topics, such as geolocation, you will be able to extend the weather application even further.
Of course, this has been a pretty quick survey. In the next few chapters, we will focus on gaining a deeper understanding of React Native best practices, and look at how to use a lot more features, too!