Upgrading from 4.x
This guide is a work in progress! As more people upgrade their apps we can continue to improve it. Please send pull requests to add any suggestions that you have from your upgrade experience.
React Navigation 5 has a completely new component based API. While the main concepts are the same, the API is different. In this guide, we aim to document all the differences so that it's easier to upgrade your app.
If you have not installed React Navigation 5 yet, you can do so following the Getting Started guide.
To reuse code using the old API with minimal changes, you can use the compatibility layer.
Before you upgrade
React Navigation 4 is still maintained and will stay compatible with the latest version of React Native. We'll accept small pull requests and release bug fixes. While we won't be actively working on new features for React Navigation 4, they may be occasionally backported.
If React Navigation 4 is working well for you and you don't need any of the new capabilities of the new version, you can keep using it. You don't have to rewrite your navigation structure.
However, if you're starting a new project, we recommend to use the latest version instead of React Navigation 4.
If you are upgrading from older versions of navigators, you should take a look at the guide for upgrading to React Navigation 4 first, especially the part for upgrading packages.
Package names
For React Navigation 5, we went with scoped packages (e.g. @react-navigation/stack
). It distinguishes them from previous versions and makes it harder to accidentally mix v4 and v5 packages. The following are the new equivalent package names:
react-navigation
->@react-navigation/native
react-navigation-stack
->@react-navigation/stack
react-navigation-tabs
->@react-navigation/bottom-tabs
,@react-navigation/material-top-tabs
react-navigation-material-bottom-tabs
->@react-navigation/material-bottom-tabs
react-navigation-drawer
->@react-navigation/drawer
Navigation Container
In React Navigation 5.x there's no createAppContainer
which provided screens with navigation context. You'll need to wrap your app with NavigationContainer provider.
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return <NavigationContainer>{/*...*/}</NavigationContainer>;
}
The onNavigationStateChange
prop on the AppContainer is now available as onStateChange
on NavigationContainer.
Configuring the navigator
In React Navigation 4.x, we used to statically configure our navigator to createXNavigator
functions. The first parameter was an object containing route configuration, and the second parameter was configuration for the navigator.
const RootStack = createStackNavigator(
{
Home: {
screen: HomeScreen,
navigationOptions: { title: 'My app' },
},
Profile: {
screen: ProfileScreen,
params: { user: 'me' },
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
gestureEnabled: false,
},
}
);
With 5.x, we now configure the navigator inside a component. First, we create Navigator
and Screen
pair using createXNavigator
and then use them to render our navigator.
The main concepts are the same. There are navigators and screens, nesting works the same, we have configuration for the navigator and options for the screen. To summarize the differences:
- All of the configuration is passed as props to the navigator
- The route configuration is done using
Screen
elements and passed as children params
becomesinitialParams
prop onScreen
navigationOptions
becomesoptions
prop onScreen
defaultNavigationOptions
becomesscreenOptions
prop onNavigator
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
function RootStack() {
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{ gestureEnabled: false }}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My app' }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
initialParams={{ user: 'me' }}
/>
</Stack.Navigator>
);
}
The navigation
prop
Separate route
prop
In React Navigation 4.x, the navigation
prop contained various helper methods as well as the current screen's state. In React Navigation 5.x, we have split the navigation
prop into 2 props: navigation
prop contains helper methods such as navigate
, goBack
etc., route
prop contains the current screen's data (previously accessed via navigation.state
).
This means, now we can access screen's params through route.params
instead of navigation.state.params
:
function ProfileScreen({ route }) {
const userId = route.params.user;
// ...
}
No more getParam
Previously we could also use navigation.getParam('someParam', 'defaultValue')
to get a param value. It addressed 2 things:
- Guard against
params
beingundefined
in some cases - Provide a default value if the
params.someParam
wasundefined
ornull
Now, the same thing can be achieved using the upcoming optional chaining and nullish coalescing operators:
navigation.getParam('someParam', 'defaultValue');
is equivalent to:
route.params?.someParam ?? 'defaultValue';
No more isFirstRouteInParent
The isFirstRouteInParent
method did a very specific job: tell you if the route is the first one in parent's state. The main purpose was to decide whether you can show a back button in a screen depending on if it's the first one.
However, it had many of shortcomings:
- It checked the
routes
array in state to determine if it's the first, which means that it won't work for other navigators such as tab navigator which keep history in a separaterouteKeyHistory
array. - Since this was a method on the navigation object, if a screen's index changed to/from the first one, it would always trigger re-render for that screen whether you use the method or not.
Now we have added a useNavigationState
which addresses many more use cases and doesn't have these shortcomings. We can implement isFirstRouteInParent
with this hook:
function useIsFirstRouteInParent() {
const route = useRoute();
const isFirstRouteInParent = useNavigationState(
(state) => state.routes[0].key === route.key
);
return isFirstRouteInParent;
}
Specifying navigationOptions
for a screen
In React Navigation 4.x, we could do the following to specify navigationOption
:
class ProfileScreen extends React.Component {
static navigationOptions = {
headerShown: false,
};
render() {
// ...
}
}
With React Navigation 5.x, we need to pass the configuration when defining the screen:
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={{ headerShown: false }}
/>
For dynamic options, the options
prop also accepts a function which receives the navigation
and route
props:
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({ title: route.params.user })}
/>
In addition to this, React Navigation 5.x has another way to configure screen dynamically based on a screen's props or state by calling navigation.setOptions
:
function SelectionScreen({ navigation }) {
const [selectionCount, setSelectionCount] = React.useState(0);
React.useLayoutEffect(() => {
navigation.setOptions({
title:
selectionCount === 0
? 'Select items'
: `${selectionCount} items selected`,
});
}, [navigation, selectionCount]);
// ...
}
But what if we want to define options statically on the component? It's less flexible to do it, but we could do it if we wanted:
class HomeScreen extends React.Component {
static navigationOptions = {
// ...
};
}
// ...
<Stack.Screen
name="Home"
component={HomeScreen}
options={HomeScreen.navigationOptions}
/>;
You might be curious, why don't we support it by default anymore if it's so easy?
- Static properties need extra code to work if you have a Higher Order Component
- You lose the ability to use props and context here, making them less flexible
- They cannot be type-checked automatically, you need to manually annotate this property
- They don't play well with Fast Refresh, as changing them doesn't trigger a re-render
- We've seen people get confused on how to use static properties when transitioning from class components to function components
Due to the numerous disadvantages with this pattern, we decided to drop it in favor of the current API.
Navigation events
In React Navigation 4.x, there were 4 navigation events to notify focus state of the screen:
willFocus
: emitted when screen comes into focusdidFocus
: emitted when the transition animation for focus finisheswillBlur
: emitted when the screen goes out of focusdidBlur
: emitted when the transition animation for blur finishes
It was confusing to decide which events to use and what each event meant. Some navigators also didn't emit events for transition animations which made the events inconsistent.
We have simplified the events in React Navigation 5.x, so now we have only focus
and blur
events which are equivalent to willFocus
and willBlur
events. These events can be listened to using the reworked event system.
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
See the docs for Navigation events for more details and examples.
In addition, there is a new useFocusEffect
hook to make it easier to perform side-effects and data fetching only when a screen is focused.
To run tasks after an animation finishes, we can use the InteractionManager
API provided by React Native. See the docs for useFocusEffect
for more details.
Many of the navigators in React Navigation 4.x also had their events such as tab press, transition start etc. exposed in navigationOptions
. They are now consolidated into the same event system as focus
and blur
events, now named tabPress
, transitionStart
, transitionEnd
etc.
To achieve the previous use cases for these events where you added listeners without rendering a screen, you can use the listeners
prop in the Screen
component to achieve the same functionality:
<Tab.Screen
name="Chat"
component={Chat}
listeners={({ navigation, route }) => ({
tabPress: (e) => {
// Prevent default action
e.preventDefault();
// Do something with the `navigation` object
navigation.navigate('AnotherPlace');
},
})}
/>
Navigating to nested screens
Previously, you could navigate to a screen deeply nested somewhere in a navigator. This was possible because the configuration was static, and all of the navigators were available on the initial startup.
With a dynamic configuration, it becomes impossible, because new navigators and screens could be added, or existing navigators and screens could be removed any time in future. In addition, navigators are initialized as needed in 5.x instead of initializing all navigators at startup, which means that a navigator may not be available to handle an action.
Because of these reasons, you now need to be more explicit when navigating to a deeply nested screen. See nesting navigators docs for more details.
Deep-linking
In React Navigation 4.x, you could specify a path
property in your screen configuration which was used for handling incoming links. This was possible because we could statically get the configuration for all of the defined path
s.
Due to dynamic configuration in 5.x, links need to be handled before we can know what to render for our navigators. So it's necessary to specify the deep link configuration separately. See the deep linking docs for more information.
Switch Navigator
The purpose of Switch Navigator was to dynamically switch between screens/navigators, mostly useful for implementing onboarding/auth flows. For example:
const AppNavigator = createStackNavigator({
Home: HomeScreen,
Settings: SettingsScreen,
});
const RootNavigator = createSwitchNavigator({
Login: LoginScreen,
App: AppNavigator,
});
And then after login:
navigation.navigate('App');
With React Navigation 5.x, we can dynamically define and alter the screen definitions of a navigator, which makes Switch Navigator unnecessary. The above pattern can be now defined declaratively:
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
{isLoggedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<Stack.Screen name="SignIn" component={SignInScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
);
}
In earlier versions of React Navigation, there were 2 ways to handle this:
- Keep multiple navigators and use switch navigator to switch the active navigator to a different one upon login (recommended)
- Reset the state of the navigator to the desired screens upon login
Both of these approaches were imperative. We needed to update the state to save your token, and then do a navigate
or reset
to change screens manually. Seems reasonable, right? But what happens when the user logs out? We need to update the state to delete the token, then navigate
or reset
again manually to show the login screen. We have to imperatively do the task twice already. Add more scenarios to this (e.g. unverified user, guest etc.) and it becomes even more complex.
But with the above approach, you can declaratively say which screens should be accessible if user is logged in and which screens shouldn't be. If the user logs in or logs out, you update the userToken
in state and the correct screens are shown automatically.
To summarize the benefits:
- No need for manually navigating to correct screen on log in or log out, correct screens are shown automatically.
- If the user is not logged in, it's impossible to navigate to screens which need the user to be logged in (e.g. from a deep link, restoring persisted state), which means you don't need to deal with inconsistent states.
- Since all our screens are under the stack navigator, we get smooth animations after log in or log out unlike the abrupt screen change with switch navigator.
So, the new approach covers more edge cased and removes the need for something like Switch Navigator. So it has been removed.
See Authentication flows for a guide on implementing authentication flows.
Global props with screenProps
In React Navigation 4.x, we could pass a prop called screenProps
which you could access in all the child navigators:
<App screenProps={{ /* some data here */ }}>
This was handy for passing global configuration such as translations, themes etc. to all screens.
However, using screenProps
had some disadvantages:
- Changing the values in
screenProps
re-renders all of the screens in the app, regardless of whether they use it or not. This can be very bad for performance, and easy mistake to make. - When using a type-checker like TypeScript, it was necessary to annotate
screenProps
every time we want to use it, which wasn't type-safe or convenient. - You could only access
screenProps
in screens. To access them in child components, you needed to pass them down as props manually. It's very inconvenient for things like translation where we often use it in a lot of components.
Due to the component based API of React Navigation 5.x, we have a much better alternative to screenProps
which doesn't have these disadvantages: React Context. Using React Context, it's possible to pass data to any child component in a performant and type-safe way, and we don't need to learn a new API!
Themes
React Navigation 4.x had basic theming support where you could specify whether to use a light or dark theme:
<App theme="dark">
It wasn't easy to customize the colors used by the built-in components such as header, tab bar etc. without extra code or repetition.
In React navigation 5.x, we have revamped the theme system for easier customization. Now you can provide a theme object with your desired colors for background, accent color etc. and it will automatically change the colors of all navigators without any extra code. See the Themes documentation for more details on how to customize the theme.
Action creators
The navigation
object has a dispatch
method used to dispatch navigation actions. Normally we don't recommend dispatching action objects, but use the existing methods such as navigation.push
, navigation.navigate
etc. But if you were importing action creators from the library, then you'll need to update your code:
NavigationActions
is nowCommonActions
, can be imported from@react-navigation/native
StackActions
,DrawerActions
etc. can be imported from@react-navigation/native
SwitchActions
is nowTabActions
. can be imported from@react-navigation/native
Signature of many actions have changed. Refer to their docs for details:
It's highly recommended to use the methods on the navigation object instead of using action creators and dispatch
. It should only be used for advanced use cases.
In addition, there have been some changes to the way the navigation actions work. These changes probably won't affect you if you didn't do any advanced tasks with these methods.
One major difference is that a lot of methods used to take some parameters for controlling which screen and navigator it should be applied to and didn't follow a specific pattern.
In this version, we have standardized this and made it possible to use with any action without the action needing to support it. The new target
and source
properties provides control over which navigator should handle an action. See docs for dispatch for more details.
You can import the action creators from the compatibility layer to preserve old behavior for the actions.
More differences in the signatures are listed below:
navigate
Previously, it was possible to pass an object { routeName, key, params }
. Now, routeName
is called just name
, so it'll be { name, key, params }
.
The navigate
action also supported child actions in the action
property in the object. We found that very few people actually used it and most found it confusing. It also complicated the code quite a bit, so we have removed this functionality.
See navigate
action docs for more details.
goBack
Previously, the goBack
method took one parameter: from
. You could pass nothing to go back from anywhere, pass null
to go back from the current screen, or a route key to go back from a specific route. It was a common source of confusion.
The new behavior of goBack
is more intuitive as it takes you back from the screen that dispatched the action. More advanced behavior can be achieved by target
and source
properties to replicate old behavior.
See goBack
action docs for more details.
setParams
Previously, the setParams
method also took an optional key
to specify which screen was setting its params. Now the source
property can be used to achieve the same functionality.
See setParams
action docs for more details.
reset
Previously, the reset
method took an array of actions to apply. This was often not intuitive. Now, we have changed reset
method to take the new state instead:
For example, this will reset the navigator's state to have one screen called Home
:
navigation.reset({
routes: [{ name: 'Home' }],
});
The reset
action is now also supported on all navigators instead of just stack.
See reset
action docs for more details.
replace
Previously, it was possible to pass an object { routeName, key, newKey, params }
. Now, routeName
is called just name
, and newKey
is called key
, so it'll be { name, key, params }
. The previous key
can be specified using the source
property.
The replace
action also supported child actions in the action
property which has been removed.
See replace
action docs for more details.
push
Previously, it was possible to pass an object { routeName, params }
. Now, routeName
is called just name
, so it'll be { name, params }
.
The push
action also supported child actions in the action
property which has been removed.
See push
action docs for more details.
pop
Previously, the pop
method used to take an object with a property called n
which specified how many screens to go back to. Now, you can directly specify the number as the first argument instead of an object.
See pop
action docs for more details.
dismiss
The dismiss
method has been removed. You can achieve similar effect with following:
navigation.getState().pop();
jumpTo
Previously, the jumpTo
method also took an optional key
to specify which screen was setting its params. Now the source
property can be used to achieve the same functionality.
See jumpTo
action docs for more details.
Custom actions
Previously, it was possible to override the router
property and its getStateForAction
property on the navigator component to implement custom actions. Due to the dynamic nature of React Navigation 5, this is not possible. However, you can implement custom helpers to achieve the same functionality. See dispatch
docs for more details.
Scrollables
React Navigation 4.x exported its own ScrollView
, FlatList
, and SectionList
components. These were wrappers around the scrollable components react-native-gesture-handler
and would scroll to top when tapping on an active tab.
However, this was very restrictive since you may want to use another scrollable implementation which we didn't wrap. So now we have a useScrollToTop
hook that can be used with any scrollable component.
Higher order components
React Navigation 4.x included higher order components such as withNavigation
and withNavigationFocus
. Now they live in the compat package.
We also have documentation on how to use the new hooks such as useFocusEffect
if you're using class components.
Navigation state in Redux
We have long recommended not to store navigation state in Redux. We have finally dropped support for storing navigation state in Redux in React Navigation 5.x.
This means you cannot store navigation state in Redux. You can still use Redux (or any other library) for managing your app state and it will work fine. See Redux integration for more info.