I have been developing a mobile-forward React web application for a while now, with the intent to port it to React Native, once I have time to dive into it. Well, now is that time!
But before I start trying to port an entire project, I’m going to build one from a blank slate, to get a better understanding of the workflow and features that React Native has to offer. This article will go through the process of going from nothing, to a running React Native project.
Preface
React Native allows you develop native mobile applications using React. As a web developer, this is huge. Without React Native, I would have to develop an application using Java for Android, then translate it to Swift for iOS. That’s a lot of work, and two additional languages! Personally, I’d like to stick to JavaScript.
This guide assumes you know your way around React already. This will be my first dive into React Native, so to fully understand what’s going on, I’m going to start with a blank slate app and build from scratch, using the Expo platform to help manage my project. More on that later. Additionally, I am using Mac OS Mojave, so if you are using Catalina or higher, I would recommend double-checking the documentation for some of the tools we’ll be using, to prevent compatibility issues.
Setup
To use the iOS simulator and Android emulator (same thing, different names), you will need to install Xcode and Android Studio. Installing Xcode is recommended even if you aren’t using the simulator, because Expo will require its tools. If you are using Mac OS Mojave or earlier, you will need to head over to Apple’s developer site to download a past version of Xcode. The App Store only allows you to download the newest version, which is not compatible with Mojave or earlier. π
Install Expo
Expo is a platform that makes it easier to develop apps with React Native by granting you lots of great tools to manage and test your project. I’m going to use the recommended set up with Expo. To install Expo globally, in your Terminal, run:
npm install -g expo-cli
Once Expo is installed, you can initialize a new project folder with:
expo init project_name
Expo will give you some options before the project folder is created: Managed Workflow or Bare Workflow. With the Managed workflow, you will have access to the full range of tools that Expo offers, and it will take care of as much complexity for you as possible. There are, however, some limitations. At the time of writing, in-app payments and Bluetooth are not supported. I am going to accept the limitations for the benefit of simplicity and to take full advantage of Expo’s tool set. If you’re not sure about what path you’d like to take, check out the comparison in Expo’s docs.
This will create a folder named project_name, containing assets that will look similar to a normal React application.
For Mac users, it’s also recommended that you install Watchman, which is used internally by React Native and can prevent potential issues.
The last step is to install the Expo client app on your mobile device, which will allow you to view your application during development. Here are the links:
Download for Android (requires Android version 5 – Lollipop or later)
Download for iOS (requires iOS 10.0 or later)
Once you have the mobile app, head back to your Terminal and start the environment by running npm start in the root of your project folder. This will start the Javascript engine, and provide you with a QR code, which you will scan with the mobile app. After scanning the QR code and letting it load for a minute, you should see a screen displaying the text “Open up App.js to start working on your app!” We did it! Now that the app is up and running, let’s start coding.
Writing the Code
To confirm that our app is live reloading, let’s open up App.js and change the code between the <Text> tags, then click save. You should see the app update instantly.
Looking at App.js, you may notice that it looks just like a normal React component. That’s because it is! But there are some differences. You may have noticed the <Text> tag, which is not HTML. React Native has its own components, which reflect the actual native components on the platform with which it will be rendered. For example, the <Text> component in React Native represents a <TextView> in Android and a <UITextView> in iOS. This is one of the benefits of using React Native; we write in one language, and it will handle the translation.
Core Components
<View> and <Text>
Now let’s add some user interaction by allowing text input. First, let’s create a custom component called LoginScreen, which will render our text inputs and allow the user to input their credentials. But what will our custom component render? Enter React Native’s Core Components. In React Native, we use the <View> tag to render content in the same way we would use an HTML div. We will wrap all kinds of things in Views. Let’s wrap the contents of our return statement with it. React Native also provides the <Text> component, which functions similarly to the HTML <p> tag.
Here is our complete custom LoginScreen component.
import React from 'react'
import { View, Text } from 'react-native';
export const LoginScreen = () => {
return (
<View>
<Text> This is the Log In screen! </Text>
</View>
)
}
export default LoginScreen
<TextInput>
React also has a Core Component for text input, called, surprisingly, TextInput. Since you’ve used React before, you know that this will need to be a controlled component. React Native provides props for that. The prop onChangeText is called when the input changes, and defaultValue determines the value. We’ll create two text inputs for email and password, controlling both by using React Hooks. Here’s what our Login component looks like now, and note the secureTextEntry boolean prop that we add to the password field:
import React, {useState} from 'react'
import {View, TextInput} from 'react-native'
export const LoginScreen = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
return (
<View>
<TextInput placeholder="email"
onChangeText={emailInput => setEmail(emailInput)}
defaultValue={email}
/>
<TextInput placeholder="password"
secureTextEntry
onChangeText={passwordInput => setPassword(passwordInput)}
defaultValue={password}
/>
</View>
)
}
<TouchableOpacity>
Next, let’s add a Log In button! For this, we could use the Button component, but there’s a better option that allows us more flexibility with styling. This component is called TouchableOpacity. This will function as a container that holds everything that we want to be “Pressable”. So, inside, we’ll add a Text element for the button’s label. TouchableOpacity also provides us with the prop onPress, which accepts a function to call when the element is pressed, just like React’s onClick.
import {View, Text, TouchableOpacity} from 'react-native'
export const LoginScreen = () => {
const handleButtonPress = () => {}
return (
<View>
<TouchableOpacity onPress={handleButtonPress} >
<Text>Button Title</Text>
</TouchableOpacity>
</View>
)
}
These four components are all we need to complete our LoginScreen. Before we move on, let’s talk a little about how we can style them.
Styling with StyleSheet
Another important different between React and React Native is that there is no CSS in React Native. Luckily, there is similar functionality using React Native’s StyleSheet. Using StyleSheet.create(), you can pass in a plain-old JavaScript object with CSS-like key/value pairs, then set the style prop of your component equal to this object. The style prop is available to every Core Component. Check out my style object below, and check out the documentation for more info:
// Styling TextInput by passing an object to its style prop:
<TextInput style={styles.textInput}> ... </TextInput>
const styles = StyleSheet.create({
textInput: {
height: 50,
fontSize: 22,
backgroundColor: 'white',
borderRadius: 10,
width: 250,
margin: 10,
paddingLeft: 10
}
})
Navigation
The next step in creating the user experience will be to allow them to navigate to a different screen. Next week, we’ll connect the Login button to a Rails back end, but for now, we can pretend a User was authenticated and display a new View after they click “Log In”.
When it comes to navigation in React, there are a few things to consider. Since we are using an Expo managed project, we will be using the community’s solution for managed Expo projects. There is also a native solution that gives you a platform-specific look and feel, which you can find here. But let us install the community library, since we’re working with Expo. In Terminal, run:
npm install @react-navigation/native @react-navigation/stack
Next, since we are using a managed Expo project, we’ll need to install its dependencies with:
expo install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
Lastly, we will need to add some code at the top of our entry file, App.js:
import 'react-native-gesture-handler';
import { NavigationContainer } from '@react-navigation/native';
Now, within our App component, we will wrap everything in our newly imported NavigationContainer:
export default function App() {
return (
<NavigationContainer>
<LoginScreen />
</NavigationContainer>
);
}
NavigationContainer manages all of our navigation, and holds the current Screen in its state. We will also need to import createStackNavigator. Using the <Stack.Screen> component, we can essentially map routes to components using the props name and component. Let’s take the above code, and change it to render our Login component using the navigator:
import { createStackNavigator } from '@react-navigation/stack';
export default function App() {
const Stack = createStackNavigator();
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
);
}
Now, our app renders our Login component with a navigation bar. Let’s add another screen to render. For this, I will create a new custom component called Home. In our Stack.Navigator component, we can specify which component renders first by passing the name of the component to the initialRouteName component:
<NavigationContainer>
<Stack.Navigator initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Home" component={Home} />
</Stack.Navigator>
</NavigationContainer>
Finally, let’s let the user navigate between pages. We’ll do this by using the onPress prop in our Log In button. The Stack.Screen component passes a prop called navigation to the component it renders. This prop uses its own function, navigate, which accepts one argument, the name of the component as we defined within our Stack.Screen component. Here is our Login component and button with navigation functionality:
import { Text, View, TouchableOpacity } from 'react-native';
export const Login = (props) => {
const handleLoginPress = () => {
props.navigation.navigate('Home')
}
return (
<View>
<TouchableOpacity onPress={handleLoginPress}>
<Text>log in</Text>
</TouchableOpacity>
</View>
)
}
Now, when you press the Log In button, you will be taken to the Home screen, complete with navigation bar and “Back” button.
Conclusion
I think this is enough to get me up and running for now. We’ve made two screens, and can navigate between them. I encourage you to check out the docs for React Native components. Now, I will focus on creating several Screens for this application, and document my process for next week. On my plate will be connecting the app to a Rails back-end, advanced routing/navigation, and animations.
We’ve only scratched the surface, and I look forward to diving deeper into React Native. The community is great and there are some amazing community-developed libraries I intend to get into. Hope you come back next week, and let me know if you liked this article!