Using react-navigation 3.0 in React Native apps

Khoa Pham
Khoa Pham
Jan 21 · 6 min read

react-navigation is probably the only dependency I use in React Native apps. I’m happy with it so far, then version 3.0 came out with a few breaking changes. It’s not that big deal but may take us sometime upgrading if we don’t pay enough attention. Here is my exploration and how to overcome the upgrading pains.

React Navigation is born from the React Native community’s need for an extensible yet easy-to-use navigation solution written entirely in JavaScript (so you can read and understand all of the source), on top of powerful native primitives.

The thing I like about react-navigation is its thorough documentation, easy to customise APIs and very nifty to use. It solves almost all basic needs.

React Navigation

This is the first release where React Navigation depends on a native module outside of React Native core: it now depends on react-native-gesture-handler. This library provides an excellent set of primitives for leveraging the operating systems’ native gesture APIs and has enabled us to fix a variety of issues with stack and drawer navigators.

I like to read source code, and I was surprised to see that react-navigation is all pure Javascript code.

module.exports = {
// Native
get createAppContainer() {
return require('@react-navigation/native').createAppContainer;
},
get createNavigationContainer() {
console.warn(
'`createNavigationContainer()` has been deprecated, please use `createAppContainer()` instead. You can also import createAppContainer directly from @react-navigation/native'
);
return require('@react-navigation/native').createAppContainer;
},
get createKeyboardAwareNavigator() {
return require('@react-navigation/native').createKeyboardAwareNavigator;
},
}

react-navigation/native refers to this separated react-navigation-native, and it in turns relies on react-navigation-core and they are all Javascript code. I thought it is just Javascript wrapper around Activity or UINavigationViewController but they aren’t. How pretty is that. All the code worths taking a look.

Also all the tab and stack navigators like react-navigation-tabs and react-navigation-stack have been moved out to separated repos for modularity sake. The router and events mechanism seem to be powered by react context in NavigationContext

import createReactContext from 'create-react-context'; 
const NavigationContext = createReactContext(undefined);
export default NavigationContext;

Upgrading to 3.0

The migration guide to 3.0 is quite straightforward, but there are quite a few gotchas that we need to be careful. Before the upgrade, I was using react-native 0.57.5 and react-navigation 2.0

Firstly, install version 3.0 together with react-native-gesture-handler

npm install react-navigation@^3.0.0
npm install react-native-gesture-handler

As brentvatne pointed out, @react-navigation/core and @react-navigation/native are installed by react-navigation . Version 3.0 is also the first time react-navigation depends on a native module outside React Native core.

react-native-gesture-handler is a set of declarative API exposing platform native touch and gesture system to React Native. You can watch more about how it is used in react-navigation in the talk It all starts with navigation

Cannot read property ‘State’ of undefined in iOS

react-native-gesture-handler has some Objective C, which is native APIs. This issue is because the library is not linked into our Xcode project. I often have problems with react-native link and CocoaPods, so I usually drag Drag RNGestureHandler.xcodeproj into Xcode. You can find that in ../node_modules/react-native-gesture-handler/ios/RNGestureHandler.xcodeproj

Then in Link Binary with Libraries, remember to select libRNGestureHandler.a

(0 , _reactNavigation.default) is not a function

In the section Explicit app container required for the root navigator, it is said that “In the past, any navigator could act as the navigation container at the top-level of your app because they were all wrapped in “navigation containers”. The navigation container, now known as an app container, is a higher-order-component that maintains the navigation state of your app and handles interacting with the outside world to turn linking events into navigation actions and so on.”.

Now an explicit application container is required. The issue pops up when we declare like this

import createAppContainer from 'react-navigation'

Taking a closer look at example code, it is like

import {
createStackNavigator,
createAppContainer
} from 'react-navigation';
const MainNavigator = createStackNavigator({...});
const App = createAppContainer(MainNavigator);

If you take a look at the code for how react-navigation top properties are exported, it is clear that createAppContainer is not default export, so we need to use curly braces.

module.exports = {
// Native
get createAppContainer() {
return require('@react-navigation/native').createAppContainer;
},
}

And the source code for createAppContainer is inside react-navigation-native gives extra information for curious readers.

/**
* Create an HOC that injects the navigation and manages the navigation state
* in case it's not passed from above.
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
* components.
*/
export default function createNavigationContainer(Component) {
class NavigationContainer extends React.Component {
subs = null;
_renderLoading() {
return this.props.renderLoadingExperimental
? this.props.renderLoadingExperimental()
: null;
}
}

Now that we can use react-navigation 3.0 like below. I usually separate navigators, so I have RootNavigator in charge of the root screen setup.

// @flowimport React from 'react'
import createAppContainer from 'react-navigation'
import makeRootNavigator from './src/screens/root/RootNavigator'
const RootNavigator = makeRootNavigator({})
const AppContainer = createAppContainer(RootNavigator)
type Props = {}export default class App extends React.Component<Props> {
render() {
return <AppContainer />
}
}

undefined is not an object evaluating ‘RNGestureHandlerModule.State in Android

Now we get this issue again, but on Android. I don’t trust react-native link, so I link library manually.

Go to app build.gradle, look for dependencies block and declare

implementation project(':react-native-gesture-handler')

Also, in settings.gradle, add

include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')

The settings.gradle file is, just like the build.gradle file, a Groovy script. Only one settings.gradle script will be executed in each build (in comparison to the build.gradle script in multi-project builds). It will be executed before any build.gradle and even before the Projectinstances are created. Therefore, it is evaluated against a Settings object. With this Settingsobject, you can add subprojects to your build, modify the parameters from the command line (StartParameter) and access the Gradle object to register lifecycle handlers. Use the file, if your settings are build-related and not necessarily project-related or require logic before possible subprojects are included.

Then in MainApplication.java , import library and declare React Native package

import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNGestureHandlerPackage(),
)
}

The react-native-gesture-handler documentation mentions something like createReactActivityDelegate but I don’t see it is needed.

@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
}
}

Clear gradle and react native cache

Cache may sometimes cause us confused, so it’s best to clear the cache during this upgrading period to make sure we start fresh.

We can clear gradle by explicitly remove caches

rm -rf $HOME/.gradle/caches/

or using gradlew

./gradlew cleanBuildCache

Also, we should invalidate react native cache for current project

npm start -- --reset-cache

That’s it, thanks for reading. May you code continue to compile.

If you like this post, consider visiting my other articles and apps 🔥

Fantageek

Simple apps that make sense

Khoa Pham

Written by

Khoa Pham

My apps https://onmyway133.github.io/

Fantageek

Fantageek

Simple apps that make sense

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade