Component Lifecycles with React Navigation in React Native
#react-native #react-navigation
4 min read
Here's another "gotcha" I came across this week as I continue to familiarise myself with React Native. Check out my previous post Transitioning from React to React Native if you're like me, and making the move from React to React Native.
Lifecycles in React Native and React Navigation
The mobile app I'm working on uses React Navigation and stack navigators. For the purposes of illustration, let's say we have 2 React components that act as pages / screens (depending if you're coming at it from the angle of web or mobile development). The first component will be called Home
and the second, Details
.
Coming from React for web development, if I navigated from Home
to the Details
page,
I would expect the Home
component to unmount, and the Details
component to mount. If I navigate back, I'd expect the Home
component to mount again at that point.
This expected component lifecycle does not behave in the same way when using stack navigation in React Native. What happens instead is as you navigate from Home
to Details
, Home
does not unmount as it remains part of the stack, even as the Details
component mounts. When you navigate back in the stack to Home
, as the component was never unmounted, it just come back into focus. Note however, that at this point, the Details
component does unmount, since it is no longer part of the stack. Check out the documentation if you'd like more information.
React Native screen events
The specific situation I came across was how to then refresh an API call, when a user navigates back to a previous screen that remains part of the stack. I'd normally just stick an API call within the useEffect
hook and rely on this to be called upon the component mounting, but this clearly doesn't work with mobile stack navigation due to the difference in lifecycle for React Native.
Turns out there are two ways to do this, both of which relies on the concept of a screen being in focus. React Native emits screen events, whereby if a user navigates to a screen, the screen is said to be in focus. (As an aside, if a user navigates away from a screen, that screen is now in blur.) We can thus listen to these events and create our desired side effects.
Method 1: Set up a manual listener
In my example, I want to make an API call whenever a particular screen component is in focus. I can do this by setting up a manual listener. This sits within React's useEffect
hook. The sequence of events is therefore:
- The component mounts.
- We set up a
focus
listener. As the screen is currently in focus, it makes an immediate API call. - We navigate to another screen. This first screen is now out of focus, but still mounted. The
useEffect
hook, and thus thefocus
listener is still in play. - We navigate back to this first screen, which means it's now in focus again. The API call is made once again.
Borrowing the example from the official docs:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Make API call here
})
return unsubscribe
}, [navigation])
return <ProfileContent />
}
Note that whenever you add a listener, you should explicitly ensure it's cleaned up upon the component unmounting. This is done with return unsubscribe
in the example above.
Method 2: Use the useFocusEffect hook
Instead of manually adding a listener, we can use the useFocusEffect
hook that's provided by React Navigation. This is similar to React's useEffect
hook but runs on a screen being in focus instead.
In my case, I wanted to make an asynchronous API call. Here's how you can set the hook up for async functions. Expanding the example from the docs:
import { useFocusEffect } from '@react-navigation/native'
function Profile() {
useFocusEffect(
React.useCallback(() => {
let isActive = true
const fetchList = async () => {
try {
const tests = (await listApiService.list()).data
if (isActive) {
setList(test)
}
} catch (_) {
setError('Something went wrong')
}
}
fetchList()
return () => {
isActive = false
}
}, []),
)
return <ProfileContent />
}
Note that you want to wrap your side effect up in a useCallback
function to ensure that your API doesn't get called unnecessarily.
Conclusion
The official docs recommends going with method 2 wherever possible. It's cleaner and is designed to integrate with React Native's component lifecycle, in addition to how React Navigation works. I also prefer the way it's written, and think it looks a lot cleaner. 🧼