- Build Your First App - intro
Every app starts with navigation. In this section, we are going to build the skeleton of our app using react-native-navigation.
React Native Navigation (RNN) is an open source project created by the Wix mobile team. It is now one of the top open source project by Wix. As opposed to other popular JS navigation solutions for react-native, RNN is a 100% native navigation solution with a simple cross-platform JS API.
This tutorial will help you learn RNN's basic functionality and feel more comfortable diving into the more complex features and API. You can view full documentation here.
- React Native Navigation docs.
- React Native Navigation on Stack Overflow.
Here's the outline of what we are going to build in this section:
- We will create a
PostsList
screen. - Tapping on a post will push the
ViewPost
screen - From the
ViewPost
screen, we can delete the post and go back to the posts list. - From the list, we also have an option to open a modal with the
AddPost
screen. - From the
AddPost
screen, we can cancel or save and go back to the posts list.
You can follow the react-native getting started guide(Choose "React Native CLI Quickstart" and not "Expo Quickstart") to make sure you have all dependencies installed. If this is not the first time you are creating a react-native project just open the terminal and run:
npx react-native init wixMobileCrashCourse
cd wixMobileCrashCourse
To run the project you will need to do:
npx react-native run-ios
npx react-native run-android
You should then see your new app running within your simulators:
iOS | Android |
---|---|
As react-native-navigation
is a native navigation library, so integrating it into your app will require editing native files. Follow the installation guides in the documentation here.
Make sure your app is still running in both simulators and that you are not getting any red screens.
❗ If you're running React Native 0.60+ make sure you install React Native Navigation v3.0.0 or later.
In src/posts/screens
create three screens: PostsList.js
, ViewPost.js
and AddPost.js
. Each screen should be a very basic component that looks like this:
With Class Components
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
class PostsList extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.text}>PostsList Screen</Text>
</View>
);
}
}
export default PostsList;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#D3EDFF',
},
text: {
fontSize: 28,
textAlign: 'center',
margin: 10,
}
});
With Functional Components
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
const PostsList = (props) => {
return (
<View style={styles.container}>
<Text style={styles.text}>PostsList Screen</Text>
</View>
);
}
export default PostsList;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#D3EDFF',
},
text: {
fontSize: 28,
textAlign: 'center',
margin: 10,
}
});
Every screen component in your app must be registered with a unique name before you are able to use it. So create a src/screens.js
file and register the new screens you have just created.
Here is what your screens.js
file should look like. (This file is also available in this repository):
import {Navigation} from 'react-native-navigation';
export function registerScreens() {
Navigation.registerComponent('blog.PostsList', () => require('./posts/screens/PostsList').default);
Navigation.registerComponent('blog.AddPost', () => require('./posts/screens/AddPost').default);
Navigation.registerComponent('blog.ViewPost', () => require('./posts/screens/ViewPost').default);
}
From your index.js
file, call registerScreens
and initialize the app layout that you want via the setRoot
command - pick the simplest layout, which would be the one based on a single stack (a stack supports child layouts of any kind; it can be initialized with more than one screen, in which case the last screen will be presented at the top of the stack) with a single component - our PostsList screen.
The possibilities of the layout API are almost endless and you can create almost any arbitrary native layout. You can check out all of the layout types here.
Here is what your index.js
should look like:
import {Navigation} from 'react-native-navigation';
import {registerScreens} from './src/screens';
registerScreens();
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: {
children: [
{
component: {
name: 'blog.PostsList',
options: {
topBar: {
title: {
text: 'Blog'
}
}
}
}
}
],
}
}
});
});
You have just set the root using a single stack with the PostsList component AND the Top Bar title provided in the Options object; You can check the complete Options object format here.
When you refresh the app, you should get the blue PostsList screen:
All actions described in this section are provided in this commit.
Now we want to enable the following behavior: when a user clicks on the text, the app pushes the ViewPost screen. Later on, it will be very easy to attach the same function to a list item instead of text.
To push a new screen into this screen’s navigation stack, we will use Navigation.push. This method expects to receive the current componentId
which can be found in props.componentId
.
So in PostsList.js
create a pushViewPostScreen
function and attach it to the onPress
event of the Text item.
Here is how PostsList.js
will look like:
With Class Components
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View, Text, StyleSheet} from 'react-native';
import {Navigation} from 'react-native-navigation';
class PostsList extends Component {
static propTypes = {
componentId: PropTypes.string
};
pushViewPostScreen = () => {
Navigation.push(this.props.componentId, {
component: {
name: 'blog.ViewPost',
passProps: {
somePropToPass: 'Some props that we are passing'
},
options: {
topBar: {
title: {
text: 'Post1'
}
}
}
}
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.text} onPress={this.pushViewPostScreen}>PostsList Screen</Text>
</View>
);
}
}
...
With Functional Components
import React, {useCallback} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {Navigation} from 'react-native-navigation';
const PostsList = (props) => {
const pushViewPostScreen = useCallback(() => {
Navigation.push(props.componentId, {
component: {
name: 'blog.ViewPost',
passProps: {
somePropToPass: 'Some props that we are passing'
},
options: {
topBar: {
title: {
text: 'Post1'
}
}
}
}
});
}, [props.componentId]);
return (
<View style={styles.container}>
<Text style={styles.text} onPress={pushViewPostScreen}>PostsList Screen</Text>
</View>
);
}
...
Several things in the code above that we didn't cover are:
- passProps - you can pass props to screen which we are pushing.
- options - you can style the appearance of the navigator and its behavior by passing any options via the Options object. This object can be declared in two different ways:
- You can declare the object dynamically when adding a screen to the layout hierarchy, as we did in the code above.
- You can also define the object by setting static
options()
on the screen component. This declaration is static and should be done for every screen. In the next section, we will explore this option.
When you refresh the app, you should now be able to push the ViewPost screen by clicking (which in real life, outside of the emulator, would be tapping) on the text:
All the steps from this section can be viewed in this commit.
On the PostsLists screen, we want to have an “Add” button that opens the AddPost screen as a modal. Buttons are part of the Options object.
Declare the button in the PostsList screen statically. Top bar buttons have multiple options for customization, but for the purposes of this course we will declare a very simple button with a title and an id.
We want the component to handle the button click, so you will need to do 2 things:
- Add
Navigation.events().bindComponent(this)
to the constructor. - When the top bar button press event is triggered, the app need to call the
navigationButtonPressed
- implement that andalert
orconsole.log
the pressed button id.
Here is how your postList.js
file will look like:
With Class Components
...
class PostsList extends Component {
constructor(props) {
super(props);
Navigation.events().bindComponent(this);
...
static options() {
return {
topBar: {
rightButtons: [
{
id: 'addPost',
text: 'Add'
}
]
}
};
}
navigationButtonPressed({buttonId}) {
alert(buttonId);
}
...
pushViewPostScreen() {
...
With Functional Components
...
const PostsList = (props) => {
useEffect(() => {
const subscription = Navigation.events().registerNavigationButtonPressedListener(
({buttonId}) => alert(buttonId),
);
return () => subscription.remove();
}, []);
...
const pushViewPostScreen = useCallback(() => {
...
return (
...
);
}, []);
PostsList.options = {
topBar: {
rightButtons: [
{
id: 'addPost',
text: 'Add'
}
]
}
};
...
Now you have an "Add" button and whenever you press it, you should get an alert (or a log message) with the buttonId
(in our example it is addPost
).
Next, instead of the just displaying the buttonId
as an alert or message, let's actually write a handle for the press event and show the AddPost screen as a modal with Navigation.showModal.
All the steps from this section can be viewed in this commit.
Flowing the same logic we used to add the Add Button, now add the Cancel and Save buttons to the Top bar of the AddPost screen. Whenever the Cancel button is clicked, use Navigation.dismissModal to dismiss the modal and go back to the PostsList screen.
❗ Left buttons on Android only support icons, so we will add an "X" icon which you can download from the /src/icons
folder.
With Class Components
...
import PropTypes from 'prop-types';
import {Navigation} from 'react-native-navigation'
class AddPost extends Component {
static propTypes = {
componentId: PropTypes.string
};
constructor(props) {
super(props);
Navigation.events().bindComponent(this);
}
static options() {
return {
topBar: {
title: {
text: 'Add Post'
},
rightButtons: [{
id: 'saveBtn',
text: 'Save'
}],
leftButtons: [{
id: 'cancelBtn',
icon: require('../../icons/x.png')
}]
}
};
}
navigationButtonPressed({buttonId}) {
if (buttonId === 'cancelBtn') {
Navigation.dismissModal(this.props.componentId);
} else if (buttonId === 'saveBtn') {
alert('saveBtn');
}
}
...
With Functional Components
...
import {Navigation} from 'react-native-navigation'
const AddPost = (props) => {
...
useEffect(() => {
const subscription = Navigation.events().registerNavigationButtonPressedListener(
({buttonId}) => {
if (buttonId === 'cancelBtn') {
Navigation.dismissModal(props.componentId);
} else if (buttonId === 'saveBtn') {
alert('saveBtn');
}
},
);
return () => subscription.remove();
}, []);
...
}
AddPost.options = {
topBar: {
title: {
text: 'Add Post'
},
rightButtons: [{
id: 'saveBtn',
text: 'Save'
}],
leftButtons: [{
id: 'cancelBtn',
icon: require('../../icons/x.png')
}]
}
}
...
All the steps from this section can be viewed in this commit.
If you look at the GIF image showing the final stage the app will have by the end of this section (at the bottom of this page), the Save
button is disabled until a user starts typing something in the TextInput.
To disable the button, we can simply add enabled: false
in the button option.
But how do we set styles dynamically? Glad you asked. Navigation.mergeOptions to the rescue!
You can pass any Options object in the mergeOptions
method, which will dynamically change a screen style.
These options are merged with the existing Options object.
Let's add a TextInput
and set the Save
Button dynamically.
This is how our AddPost
screen will look like:
With Class Components
...
import {View, Text, TextInput, StyleSheet} from 'react-native';
...
class AddPost extends Component {
...
static options() {
return {
topBar: {
title: {
text: 'Add Post'
},
rightButtons: [{
id: 'saveBtn',
text: 'Save',
enabled: false
}],
leftButtons: [{
id: 'cancelBtn',
icon: require('../../icons/x.icon.png')
}]
}
};
}
...
onChangeText = text => {
Navigation.mergeOptions(this.props.componentId, {
topBar: {
rightButtons: [{
id: 'saveBtn',
text: 'Save',
enabled: !!text
}]
}
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.text}>AddPost Screen</Text>
<TextInput
placeholder="Start writing to enable the save btn"
onChangeText={this.onChangeText}
/>
</View>
);
}
}
export default AddPost;
With Functional Components
...
import {View, Text, TextInput, StyleSheet} from 'react-native';
...
const AddPost = (props) => {
...
const [text, setText] = useState('');
const onChangeText = useCallback(text => {
setText(text);
Navigation.mergeOptions(props.componentId, {
topBar: {
rightButtons: [{
id: 'saveBtn',
text: 'Save',
enabled: !!text
}]
}
});
}, [props.componentId]);
return (
<View style={styles.container}>
<Text style={styles.text}>AddPost Screen</Text>
<TextInput
placeholder="Start writing to enable the save btn"
value={text}
onChangeText={onChangeText}
/>
</View>
);
}
AddPost.options = {
topBar: {
title: {
text: 'Add Post'
},
rightButtons: [{
id: 'saveBtn',
text: 'Save',
enabled: false
}],
leftButtons: [{
id: 'cancelBtn',
icon: require('../../icons/x.icon.png')
}]
}
}
export default AddPost;
You can check this commit.
The app navigation backbone is almost ready and we will leave the rest to you.
- Save - Dismiss the modal when clicking on the
Save
button in the same way the Cancel button does (won't actually do any saving for now). - Delete - In The
ViewPost
screen, add theDelete
button. Use Navigation.pop to pop the ViewPost screen from the stack and reveal the PostsList screen underneath (again, not actually deleting anything just yet).
You can check out these commits if you need a hint: Save Button, Delete Button.
Your app should now look something like this:
By this point you have:
- Became familiar with a part of react-native-navigation API.
- Registered some screens.
- Initialised the app layout via the setRoot command
- Pushed screens using Navigation.push
- Learned how to set the Top Bar buttons and how to handle navigation press events
- Learned how to work with Modals, and
- Learned how to style the navigator using the Options object and how to dynamically merge options.
You can view the full project in this repository. Also you can find class components version in this branch