After having not really good experience with using out-of-the-box navigation for React Native (it was good as long as I was not trying to customize something) I decided to create navigation by myself. When I told to my colleague that I want to make mobile app navigation from scratch he made that (☉☉) kind of face, but now I know that was one of my best decisions ever made.
In this article I’ll describe how to make mobile app navigation from scratch and how easy was to resolve problems which gave me a headache in case of out-of-the-box navigation.
I’ve created new React Native project running following command:
react-native init Navigation
Of course I had everything installed and setup before.
Now it’s time to install Regux with few additions to it (middleware and logger) by simply running:
npm install — save react-redux redux redux-logger redux-thunk
I’m not sure if I’ll use middleware but let’s lave it for now. In the next step I’ve setup Redux basing on (IMO really great) lesson from Jon Lebensold. I basically crated following files (reducers.js, createReducer.js, index.js, types.js) and I wired up Redux on main index.js file.
Now it’s time to navigate! Let’s define some screens:
Pretty simple, three screens should be enough for now. I’ve created simple React component for each of the screen. Which of those component should be rendered will be determined by navigation store’s state. Let’s define the reducer:
Initial state for our store is set to screen called
TOP (line 2).
NAVIGATIONCHANGE handler does one extra thing: it pushes current screen to the history array (line 9). We will focus on that later to handle navigating back to previous screen.
We also need an action creator for this reducer, let’s call it
navigate() and dispatch our event inside.
Now let’s create
navigation component which will be responsible for rendering screens basing on the data from store.
In this component we’re reading current screen from navigation store (it’s bound to
props on line 27) and rendering actual screen component (
switch statement on line 4).
It’s not a surprise that screen called
TOP is rendered. And this is not because we set default on the switch statement above. That’s because we set
TOP screen as initial state in
navigationreducer.js. Feel free to change it and see the result.
Now let’s make an use from
navigate action we created. I’ll make a
menu component and place it at the bottom of the screen. A little bit of styling, material design colors, simple icons made with Figma and viola!
And here’s how menu component looks like:
No rocket science there. We defined three menu options with different screen and icon assigned to each, rendered them in the loop and when taping on them calling Redux action which updates the store and this results in different screen being rendered by
navigation.js. That’s it!
When rendering we can do funky stuff like setting different color for active menu item, et cetera.
Now I’ll describe some cases which were problematic to achieve with out-of-the-box navigation.
Just to give user more space to operate when half of the screen is covered by the keyboard. While we are on
menu.js let’s extend it with following listeners from RN Keaybord class:
We basically update
hideMenu flag and when it’s set to
true render does nothing. As simple as that.
As you probably remember we do keep navigation history in our store. Let’s make an use of it! We need new action with simple dispatch:
and inside our reducer a handler for this action which will take last item from our history and set it as a current screen:
Notice that on line 6 we remove last item from history array. That’s important because without that we can only go back once!
navigateBack() action is ready to use. Let’s override Android’s back button behavior on
We can use
navigateBack() action anywhere. In example below it’s also called from simple button tap.
That’s piece of cake 🍰. Let’s say we need new screen called details:
Two more things we need to add are component for details screen and extending
navigation.js with following:
We can even make that simpler and render screens dynamically, right? Feel free to open a pull request!
But this should also work. Calling
this.props.navigate(SCREENS.DETAILS) from any component will move us to the details screen.
No menu option is shown as active when details screen is rendered, and that’s expected because we have no menu item for this screen. But the best thing is that nothing stops us from making an existing menu item active when details screen is shown and you can probably imagine how easy it is!
Imagine that we have a screen for adding a record and we don’t want to give an ability to get back to it. Record has been added, another screen rendered, showing add record screen again when pressing back might result in user creating a duplicate.
How can we cope with that? Well, in many ways actually as we have full flexibility, but let’s try following:
On line 5 we simply defined a list of screen that should not be remembered and if case of navigating to any screen from this list we’ll not push it to the history. And that’s it. In this particular case it’s only
DETAILS screen introduced in previous step and from now we can’t go back to it!
- Making navigation from scratch was not that hard and definitely worth it; full control on what’s going on is a huge benefit;
- Redux made it incredibly easy! With single source of truth I simply didn’t had to worry how components communicate between each other which is quite important in case of navigation;
Here’s full source code. Feel free to use it as a boilerplate for your project with custom navigation.
I understand that some things could be done better and I’m more than happy to see some comments 😊
I would like to continue this article with one of the following:
- animating screen change;
- navigating using gestures;
Any preference? Or maybe something else?