Navigation lifecycle
In a previous section, we worked with a stack navigator that has two screens (Home
and Profile
) and learned how to use navigation.navigate('RouteName')
to navigate between the screens.
An important question in this context is: what happens with Home
when we navigate away from it, or when we come back to it? How does a screen find out that a user is leaving it or coming back to it?
If you are coming to react-navigation from a web background, you may assume that when the user navigates from route A
to route B
, A
will unmount (its componentWillUnmount
is called) and A
will mount again when the user comes back to it. While these React lifecycle methods are still valid and are used in React Navigation, their usage differs from the web. This is driven by the more complex needs of mobile navigation.
Example scenario
Consider a stack navigator with 2 screens: Home
and Profile
. When we first render the navigator, the Home
screen is mounted, i.e. its useEffect
or componentDidMount
is called. When we navigate to Profile
, now Profile
is mounted and its useEffect
or componentDidMount
is called. But nothing happens to Home
- it remains mounted in the stack. The cleanup function returned by useEffect
or componentWillUnmount
is not called.
When we go back from Profile
to Home
, Profile
is unmounted and its useEffect
cleanup or componentWillUnmount
is called. But Home
is not mounted again - it remained mounted the whole time - and its useEffect
or componentDidMount
is not called.
Similar results can be observed (in combination) with other navigators as well. Consider a tab navigator with two tabs, where each tab is a stack navigator:
- Static
- Dynamic
const SettingsStack = createNativeStackNavigator({
screens: {
Settings: SettingsScreen,
Profile: ProfileScreen,
},
});
const HomeStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});
const MyTabs = createBottomTabNavigator({
screenOptions: {
headerShown: false,
},
screens: {
First: SettingsStack,
Second: HomeStack,
},
});
function FirstScreen() {
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
<SettingsStack.Screen name="Profile" component={ProfileScreen} />
</SettingsStack.Navigator>
);
}
function SecondScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
}
function Root() {
return (
<MyTabs.Navigator screenOptions={{ headerShown: false }}>
<MyTabs.Screen name="First" component={FirstScreen} />
<MyTabs.Screen name="Second" component={SecondScreen} />
</MyTabs.Navigator>
);
}
We start on the HomeScreen
and navigate to DetailsScreen
. Then we use the tab bar to switch to the SettingsScreen
and navigate to ProfileScreen
. After this sequence of operations is done, all 4 of the screens are mounted! If you use the tab bar to switch back to the HomeStack
, you'll notice you'll be presented with the DetailsScreen
- the navigation state of the HomeStack
has been preserved!
React Navigation lifecycle events
Now that we understand how React lifecycle methods work in React Navigation, let's answer the question we asked at the beginning: "How do we find out that a user is leaving (blur) it or coming back to it (focus)?"
React Navigation emits events to screen components that subscribe to them. We can listen to focus
and blur
events to know when a screen comes into focus or goes out of focus respectively.
Example:
- Static
- Dynamic
function ProfileScreen() {
const navigation = useNavigation();
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log('ProfileScreen focused');
});
return unsubscribe;
}, [navigation]);
React.useEffect(() => {
const unsubscribe = navigation.addListener('blur', () => {
console.log('ProfileScreen blurred');
});
return unsubscribe;
}, [navigation]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
</View>
);
}
function ProfileScreen() {
const navigation = useNavigation();
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
console.log('ProfileScreen focused');
});
return unsubscribe;
}, [navigation]);
React.useEffect(() => {
const unsubscribe = navigation.addListener('blur', () => {
console.log('ProfileScreen blurred');
});
return unsubscribe;
}, [navigation]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
</View>
);
}
See Navigation events for more details on the available events and the API usage.
For performing side effects, we can use the useFocusEffect
hook instead of subscribing to events. It's like React's useEffect
hook, but it ties into the navigation lifecycle.
Example:
- Static
- Dynamic
import { useFocusEffect } from '@react-navigation/native';
function ProfileScreen() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
console.log('ProfileScreen focus effect');
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
console.log('ProfileScreen focus effect cleanup');
};
}, [])
);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
</View>
);
}
import { useFocusEffect } from '@react-navigation/native';
function ProfileScreen() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
console.log('ProfileScreen focus effect');
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
console.log('ProfileScreen focus effect cleanup');
};
}, [])
);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
</View>
);
}
If you want to render different things based on if the screen is focused or not, you can use the useIsFocused
hook which returns a boolean indicating whether the screen is focused.
If you want to know if the screen is focused or not inside of an event listener, you can use the navigation.isFocused()
method. Note that using this method doesn't trigger a re-render like the useIsFocused
hook does, so it is not suitable for rendering different things based on focus state.
Summary
- React Navigation does not unmount screens when navigating away from them
- The
useFocusEffect
hook is analogous to React'suseEffect
but is tied to the navigation lifecycle instead of the component lifecycle. - The
useIsFocused
hook andnavigation.isFocused()
method can be used to determine if a screen is currently focused. - React Navigation emits
focus
andblur
events that can be listened to when a screen comes into focus or goes out of focus.