In React Navigation 5, we use conditional rendering to show auth screens when user is not logged in. This works well, but makes it difficult to use when using it with deep links.
Consider the following flow:
- User opens the app from a deep link to a screen which is only accessible to logged in users
- React Navigation tries to handle the deep link
- The user is not logged in, so the
initialState
from the deep link is ignored, and the login screen is shown - After user logs in, React Navigation switches to home screen due to conditional rendering
The user would expect to be on the screen they came to from the deep-link, since it was already handled, it doesn't happen again.
We need to somehow tell React Navigation to handle the last received deep link again. This needs to happen before the conditional rendering happens.
The biggest problem is, that deep link is handled at NavigationContainer
level, but conditional rendering happens at Navigator
level. Since 2 different components handle the rendering logic, it's not possible to handle them together. We cannot move the conditional rendering logic to NavigationContainer
level, so the only option is to handle the deep link at the Navigator
level.
The other issue is that currently there's no way to control what happens when the list of screens change due to conditional rendering. The router takes care of handling this logic. To be able to handle it differently for deep links, we need to be able to control the resulting state on conditional render.
First, we need to be able to control what happens on conditional render from inside a screen. It could look something like this:
navigation.setStateForNextRouteNamesChange(previousState => {
// ...
return nextState;
});
This would store a temporary callback to use instead of router.getStateForRouteNamesChange
in useNavigationBuilder
. The callback would be stored in a ref and cleared in an effect after it's handled. This is similar to the API LayoutAnimation.configureNext()
in the sense that it observes the next state change and acts on that.
NOTE: This implementation may not be correct for concurrent mode.
Next, we need to handle the deep link at the Navigator
level. For that we need to track the last received deep link. Let's say we have a helper called getLastLinkingUrl
to get it.
const { options } = React.useContext(LinkingContext);
// ...
const handleLastLinkingUrl = () => {
const url = getLastLinkingUrl();
// First we parse the URL to get the desired state
const getStateFromPathHelper = options?.getStateFromPath ?? getStateFromPath;
const rootState = getStateFromPath(url, options?.config);
// Then we traverse the root state and find the part of the state that corresponds to this navigator
const state = TODO;
// Once we have the state, we can tell React Navigation to use it for next route names change (conditional rendering logic change)
navigation.setStateForNextRouteNamesChange(() => state);
};
The above logic would be exported in a custom hook for a simpler API for the user.