Comments (46)
for example.
Let's say you got an email from netflix.
In the email, You click on the movie you wanted to see, but you are not signed in.
So you go to login screen instead of the movie screen and after login you go to the movie screen not home screen.
from react-navigation.github.io.
Hey all - think I may have at least a partial solution here. With @react-navigation/[email protected]
I was able to get deep link redirection to the authentication screen working if the user isn't logged in, and I have an idea about how you could implement continuing to the original deep link after login is completed too. However, to do this you have to use >=v5.8.x
First, follow their guide on setting up authentication flows here. Specifically, set up your navigation screens so that if the user is logged out, the non-authentication screens won't even be rendered at all within your navigators
const App = () => {
return (
<NavigationContainer>
<RootNavigator/>
</NavigationContainer>
)
}
const RootNavigator = () => {
// ...
// Some useState and useEffect code for setting and retrieving
// the accessToken from AsyncStorage and checking it isn't expired
// ...
const isAuthenticated = !!someState.accessToken;
return (
<Stack.Navigator>
{!isAuthenticated ? (
<Stack.Screen name="Login" component={LoginScreen}/>
) : (
<>
<Stack.Screen name="Home" component={HomeScreen}/>
<Stack.Screen name="Settings" component={SettingsScreen}/>
</>
)}
</Stack.Navigator>
);
}
Next, we can use the NavigationContainer
's linking
prop to specify how we want our deep links to map to our screens/routes, as outlined here. We will also specify the prefixes
that we want react-navigation
to allow for deep links. Note that the deep link config object has to match the navigation structure of our app
This configuration object now accepts *
to define a NotFound
screen, that will be rendered if a user tries to navigate to a deep link path that doesn't exist. Because we are conditionally rendering our screens based on the authentication state of the user, we can use this option to set the LoginScreen
as the default screen when a deep link doesn't match a screen's route
const App = () => {
return (
<NavigationContainer
linking={{
prefixes: ['someapp://', 'https://dynamiclinks.someapp.com'],
config: {
screens: {
Login: '*',
Home: {
path: 'home'
},
Settings: {
path: 'settings'
}
}
}
}}
>
// ...
</NavigationContainer>
)
}
Now if a user opens the app with the deep link someapp://home
and they aren't logged in anymore, they will be taken to the Login
screen because the Home
screen won't be rendered at all in our navigation structure. If the user was logged in, the screen will exist and they will be taken there
Note that this approach won't work fully if you want to be able to deep link a user to the Login
screen while they are still logged in, as that screen won't be rendered. Also note there won't be problems with navigating logged in users to stale deep links (like user deletable content) because the login screen isn't rendered and the wildcard fallback will fail gracefully
I didn't have time to do the second part of this, which is "forwarding" the initial deep link to our Login
component, so that it can know whether the user opened the app via deep link and wishes to continue to a specific screen other than the Home
screen (e.g. the Settings
screen). However, I think this could be easily done by:
- Add a new state variable to the
App
component namedinitialDeepLink
- Register the
NavigationContainer
'sgetInitialUrl()
prop added inv5.8.x
(documented here) - Inside that method, grab the initial deep link by the default method
await Linking.getInitialUrl()
(or w/e works for your app) - Update the new
initialDeepLink
state variable (causing single re-render) - Setup a
React.createContext()
object specifically for passing the initial deep link, something likeInitialDeepLinkContext
- Register the
<InitialDeepLinkContext.Provider value={initialDeepLink}>
in theApp
component as a parent of theRootNavigator
(in case of example above) - Access the initial deep link from the
Login
screen using React context:const initialDeepLink = useContext(InitialDeepLinkContext)
- Check if the
initialDeepLink
is null or empty, if so, define a default deep link to navigate to (e.g.someapp://home
), something likeconst initialOrDefaultDeepLink = initialDeepLink || 'someapp://home';
- Use the newer
useLinkingProps
hook fromreact-navigation
to create anonPress
handler for navigating via deep link from a component:const { onPress: navigateInsideApp } = useLinkingProps({ to: initialOrDefaultDeepLink }}
- When the user presses your login button, call
navigateInsideApp()
to navigate to the default screen, or continue following an initial deep link - (?) Somehow clean-up the
initialDeepLink
state tonull
so that logging out and back in doesn't take the user to settings instead of home. Also would prevent a horrible UX-loop if the initial link is to deleted content...
Long-winded post but hopefully this helps someone out!
EDIT: Added some more findings with this approach. I think there also might be a better solution by hoisting auth state up into the App
component to conditionally update the deep link config, etc.
from react-navigation.github.io.
Anything for react navigation 5?
from react-navigation.github.io.
@brentvatne
This question is about Scenarios like below
I have a URL that points to the app and i open it, now that URL is suppose to take me to the area of the application that is not accessible without authentication. (this can also be from notifications)
How to handle those scenarios with deep linking with react-navigation?
from react-navigation.github.io.
From what ive read, i cant seem to find any "official/correct" way to handle this scenario.
The way i see it, there are 2 ways to handle deep linking to a specific screen with auth flow.
- Navigation happens automatically
- Navigation happens manually
And i think the automatic is obviously the prefered one.
For example on the auth flow docs: https://reactnavigation.org/docs/auth-flow/
There is this section: https://reactnavigation.org/docs/auth-flow/#dont-manually-navigate-when-conditionally-rendering-screens
Which explains why you shouldt navigate manually, but let react-navigation navigate when the auth status changes.
On the configuring links docs which explains how to handle deep linking: https://reactnavigation.org/docs/configuring-links
it says: When you specify the linking prop, React Navigation will handle incoming links automatically.
Both documentations emphasize that you should not navigate manually, instead set it up correctly so that react-navigation can handle it.
However, i cannot find any documentation for handling deep linking with auth flow. in other words, how to handle deep linking when certain screens are not initially available.(which brought me and others to this thread).
Since no "official" way of doing this is documented, there generally isent a sufficient answer for these questions on stackoverflow for example. and any example i can find seems to implement their own custom solution, perhaps where they navigate manually. (I personally worked on a project previously where the deep linking was handled manually. Which was a mess. They manually ran a regex on the deep link to grab the query params)
Which totally goes against what the docs recommend doing.
From what i see, developers have to pick 1 of them to be handled automatically, while the other has to be handled manually.
So im curious, what is the ideal thing to do here? Custom implementations seem to have been the solution for the past few years.
from react-navigation.github.io.
nope, i'd suggest just opting out of react-navigation's automatic url handling and handle the deep link on your own.
const AppContainer = createAppContainer(/* your stuff here */);
export default class App extends React.Component {
/* Add your own deep linking logic here */
render() {
return <AppContainer enableURLHandling={false} />
}
}
from react-navigation.github.io.
I found an easier way, i'm maintaining the linking in a separate file and importing it in the main App.js
linking.js
const config = {
screens: {
Home:'home',
Profile:'profile,
},
};
const linking = {
prefixes: ['demo://app'],
config,
};
export default linking;
App.js
& during login I keep the token inside async storage and when user logs out the token is deleted. Based on the availability of token i'm attaching the linking to navigation and detaching it using state & when its detached it falls-back to SplashScreen.
Make sure to set initialRouteName="SplashScreen"
import React, {useState, useEffect} from 'react';
import {Linking} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {createStackNavigator} from '@react-navigation/stack';
import {NavigationContainer} from '@react-navigation/native';
import linking from './utils/linking';
import {Home, Profile, SplashScreen} from './components';
const Stack = createStackNavigator();
// This will be used to retrieve the AsyncStorage String value
const getData = async (key) => {
try {
const value = await AsyncStorage.getItem(key);
return value != null ? value : '';
} catch (error) {
console.error(`Error Caught while getting async storage data: ${error}`);
}
};
function _handleOpenUrl(event) {
console.log('handleOpenUrl', event.url);
}
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// Checks if the user is logged in or not, if not logged in then
// the app prevents the access to deep link & falls back to splash screen.
getData('studentToken').then((token) => {
if (token === '' || token === undefined) setIsLoggedIn(false);
else setIsLoggedIn(true);
});
Linking.addEventListener('url', _handleOpenUrl);
return () => {
Linking.removeEventListener('url', _handleOpenUrl);
};
}, []);
return (
//linking is enabled only if the user is logged in
<NavigationContainer linking={isLoggedIn && linking}>
<Stack.Navigator
initialRouteName="SplashScreen"
screenOptions={{...TransitionPresets.SlideFromRightIOS}}>
<Stack.Screen
name="SplashScreen"
component={SplashScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="Home"
component={Home}
options={{headerShown: false, gestureEnabled: false}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{headerShown: false, gestureEnabled: false}}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
When a logged in user opens the deep link from notification then it will take him to the respective deep linked screen, if he's not logged in then it will open from splash screen.
from react-navigation.github.io.
Tried all the solutions above, this works for me finally:
Create linking config to NavigationContainer, pass linking as a prop.
- When open the app with deep link, store the deeplink using Linking (react-native) and useEffect Hooks (I use redux)
- After auth process, since you already have the deepLink, manually call
Linking.openUrl(linkYouStored)
, then react-navigation will handle this link using your linking configuration.
from react-navigation.github.io.
I'm not sure if anyone has ran into this related issue (maybe this is what @mehmetcansahin is referring to when they say conditional auth stack doesn't work) but when opening a deep link for a closed app, we actually get an undefined exception if the user is not logged in useDescriptors.tsx
at line 153
The exception is
undefined is not an object (evaluating 'config.props')
>((acc, route, i) => {
const config = screens[route.name];
const screen = config.props;
const navigation = navigations[route.key];
const optionsList = [
I would imagine this should fail more gracefully in general but our work around is somewhat obvious: We just don't include the routes we need authentication for in our linking config:
if (!isLoggedIn) {
return ({
prefixes: [],
config: {
screens: {
Login: "login"
}
}
});
}
return ({
config: {
screens: {
Home: "home",
// All other routes here
}
},
})
Now back to the original post, where you might want to navigate to that initial url after login (if it is valid).
For react navigation v6, I imagine you could store the initial url from getInitialURL
in state and then using a useEffect on isLoggedIn
update the navigation state for that url? using getStateFromPathDefault
?
Sorry for rambling, may try this later. Am sort of surprised there isn't really an established pattern for this though.
from react-navigation.github.io.
hmm i guess you are right. i didnt think about that. since in my case i was just trying to get it to work with auth flow.
i havent had to do this yet, but i assume to do this you would have to save the users deep link string. then after they are authenticated, you would run Linking.openUrl()
on the saved deep link.
so if the deep link is: myapp://movies/123
, maybe you would save that string in AsyncStorage/Redux. then when they are authenticated and you render the correct screen/stack, you call Linking.openUrl(myapp://movies/123)
and then most likely clear the string wherever you saved it, after running the deep link.
dont know if there are any issues with saving the deep link like this, but i assume you have to persist the string somehow for a few seconds at least, while the authentication happens.
a problem i can see with this approach is that you would (maybe) need to find a way to only run this function if you know the deep link should run only if the user needs to log in. since you have no reason to run it if they are logged in already.
from react-navigation.github.io.
Hi! I just found a nice solution on StackOverflow, it goes with something like:
const MainNavigation = createSwitchNavigator(
{
SplashLoading,
Onboarding: OnboardingStackNavigator,
App: AppNavigator,
},
{
initialRouteName: 'SplashLoading',
}
);
const previousGetActionForPathAndParams =
MainNavigation.router.getActionForPathAndParams;
Object.assign(MainNavigation.router, {
getActionForPathAndParams(path: string, params: any) {
const isAuthLink = path.startsWith('auth-link');
if (isAuthLink) {
return NavigationActions.navigate({
routeName: 'SplashLoading',
params: { ...params, path },
});
}
return previousGetActionForPathAndParams(path, params);
},
});
export const AppNavigation = createAppContainer(MainNavigation);
Where, if link requires auth, it redirects to SplashLoading, forwarding initial path and params so it can recover later, when authenticated, the initially wanted user flow.
from react-navigation.github.io.
@chas-ps generally, after login, the RootNavigator will undergo a state change since isAuthenticated
is likely changed.
We are experiencing an issue where we are trying to navigate to an authenticated screen but the authenticated state is updated after we try to navigate causing the NavigationContainer to be re-rendered and thus losing our new navigation state.
We have been struggling with this for a few days, is there any combination of hooks that we can use to listen to be sure the RootNavigator is in the authenticated state, then navigate to our desired screen?
from react-navigation.github.io.
is there any combination of hooks that we can use to listen to be sure the RootNavigator is in the authenticated state, then navigate to our desired screen
The useEffect
hook in the component containing the container should fire after that component finishes re-rendering (commit phase), which would include all children re-rendering as well. However, instead of manually navigating to a screen, you could just sort your screens so that the screen you want to be focused is the first one in the conditionl, and it would automatically happen after re-rendering.
from react-navigation.github.io.
Quick summary of what I did, maybe it could help others.
I followed a different path from what @chas-ps tried (that probably works) :
- While I am doing the authentication call to my server (
is_authentication
is null), I keep the bootsplash shown - As long as
is_authentication
is null, I DO NOT RENDER the NavigationContainer (I simply return null, it's not an issue as the splashscreen is displayed) - When my authentication call is over, is_authentication is true or false, and then I can render the NavigationContainer
- This is kind of a workaround, but it allows "waiting for the authentication status to be known" before rendering the whole NavigationContainer, so when the linking configuration is loaded by react-navigation, the status is already known
from react-navigation.github.io.
I was able to work around this issue by using the same route name for AuthStack initial screen and Authenticated Stack screen.
Something like this
isSignedIn ? (
<>
<Stack.Screen name="Initial" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="Initial" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
Then in my linking definition
const config = {
screens: {
Initial: 'app/:some-special-screen-with-params',
},
};
if the app is running and authenticated, it would pass the parameters to the HomeScreen.
if the app is not running or not authenticated, it would pass the parameters to the SignInScreen and after signing handle the parameters accordingly.
from react-navigation.github.io.
@muiruri smart workaround ! :)
from react-navigation.github.io.
so i think i found something that works where you can have both automatic auth flow and automatic deep linking.
im using react-navigaton v6, but v5 should be the same. dont know about v4 and earlier, but i would hope at this point that most projects would be updated to at least v5.
The problem
the problem here is basically that we render the <NavigationContainer>
BEFORE we know if the user is logged-in or not. so even if we correctly conditionally render the individual screens, we still render the whole <NavigationContainer>
.
this is a problem because the initial authentication when the user opens the app isent done when we render the <NavigationContainer>
. this means the linking
prop takes effect right away, but since the authentication hasent happened yet, we can never be taken to a screen for logged-in users.
so we have to make sure we know if the user is logged-in or not, before rendering the <NavigationContainer>
with the linking prop. this way it can correctly automatically take us to the screen for logged-in users.
i will show a stripped-down version of how the setup was for me:
- reduce amount of repetitive boilerplate when there are multiple screens
- most imports which are specific to my project are removed
- typescript.
then i will show the changes i made to get it working.
my App.tsx just returns a <AppNavigationContainer>
which is where my <NavigationContainer>
is.
Initial setup
AppNavigationContainer.tsx:
import React, { useEffect } from 'react';
import { Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import NativeSplashScreen from 'react-native-splash-screen';
import RootStackNavigator from './rootStack';
export const AppNavigationContainer = () => {
useEffect(() => {
setTimeout(() => {
NativeSplashScreen.hide();
}, 500);
}, []);
const linking = {
// todo: NotFound screen
prefixes: ['myprefix://'],
config: {
initialRouteName: 'Test',
screens: {
Test: 'test',
BenefitsGuide: 'benefitsGuide',
Main: {
initialRouteName: 'Home',
screens: {
Membership: 'membership',
Usages: 'usages',
},
},
},
},
};
return (
<NavigationContainer
linking={linking}
fallback={(
<Text>
fallback component here
</Text>
)}
>
<RootStackNavigator />
</NavigationContainer>
);
};
my <RootStackNavigator>
is just a simple createStackNavigator()
with some screens that are available to users at all times(such as "Terms" and "Error"). its not really relevant but i will post it just to clear any possible confustion:
RootStackNavigator.tsx:
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import MainStackNavigator from '../mainStack';
const RootStack = createStackNavigator();
const RootStackNavigator = () => {
return (
<RootStack.Navigator
initialRouteName="Main"
>
<RootStack.Screen
name="Main"
component={MainStackNavigator}
/>
{/* Benefits */}
<RootStack.Screen
name="BenefitsGuide"
component={BenefitsGuideScreen}
/>
{/* other screens here */}
</RootStack.Navigator>
);
};
export default RootStackNavigator;
now as you can see in my linking configuration, it follows the setup.
one of the screens is called "Main", which is where i have another createStackNavigator()
. this is where the actual conditional rendering happens.
MainStackNavigator.tsx:
import React, { useEffect } from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { useSelector } from 'react-redux';
const MainStack = createStackNavigator();
const MainStackNavigator = () => {
const auth = useSelector(authSelector);
const splash = useSelector(splashSelector);
return (
<MainStack.Navigator>
{/* Splash */}
{splash.isLoading && (
<MainStack.Screen
name="Splash"
component={SplashScreen}
/>
)}
{/* Authentication */}
{auth.isLoading || !auth.isLoggedIn ? (
<MainStack.Screen
name="SignIn"
component={SignInScreen}
/>
) : (
<>
{/* Home */}
<MainStack.Screen
name="Home"
component={HomeScreen}
/>
{/* Usages */}
<MainStack.Screen
name="Usages"
component={UsagesScreen}
/>
{/* Membership */}
<MainStack.Screen
name="Membership"
component={MembershipScreen}
/>
{/* other screens here */}
</>
)}
</MainStack.Navigator>
);
};
export default MainStackNavigator;
so pretty standard here. i show a custom <SplashScreen>
component. this screen shows the user an animation while they are being authenticated, to determine if they are logged-in or not. not to be confused with a native splash screen. this app uses react-native-splash-screen
to show a native splash screen.
then we conditionally render the screens depending on the users auth status. i use redux so the variables are pulled in from there. not logged-in shows the <Signin>
screen and logged-in are shown the <Home>
screen.
your setup may be sligtly different, but i suspect it will be at least somewhat similar.
at this point, the only thing that works is just opening the app with a deep link or opening one of the screens that are always available. in my example here im showing the <UsagesScreen>
and <MembershipScreen>
, and i cant deep link to those.
so to test it out on ios my deep link is:
npx uri-scheme open myprefix:// --ios
this opens the app.
i can also open a screen that is available at all times:
npx uri-scheme open myprefix://benefitsGuide --ios
and there is another problem for my app specifically. i previously mentioned that the authentication happens in my <SplashScreen>
component. by doing this i never see this component. due to the deep link i open the specified screen.
when i normally open the app it goes like this:
- shows a native splash screen with
react-native-splash-screen
- show my custom
<SplashScreen>
component - show screen depending on auth status
when i deep like now it goes like this:
- shows a native splash screen with
react-native-splash-screen
- shows screen i deep linked to.
so i dont get the animation on my custom <SplashScreen>
component. but most importantly i dont get the authentication code i have in that file. and again, that is just me setup. you may not have a custom <SplashScreen>
component, and you may not run any authentication logic in there.
but lets say the user is logged-in and i want to open the "usages" screen. i would do:
npx uri-scheme open myprefix://usages --ios
this wont work. i still just be shown the <HomeScreen>
. the reason for this is, as i mentioned earlier, that the user is not yet authenticated when we render the <NavigationContainer>
with the linking
prop.
Solution
so the solution wasent really that complicated(in my case). as i have mentioned, the recurring problem was rendering my <NavigationContainer>
before we authenticate the user.
so since in my app, the authentication happens in my custom <SplashScreen>
component, i have to render that before my <NavigationContainer>
. while it is currently a screen in my <NavigationContainer>
.
i basically just had to move my custom <SplashScreen>
component. it could no longer be a part of my <NavigationContainer>
.
instead i had to early return the custom <SplashScreen>
component before the <NavigationContainer>
.
Updated files
MainStackNavigator.tsx:
remove custom <SplashScreen>
component
- {/* Splash */}
- {splash.isLoading && (
- <MainStack.Screen
- name="Splash"
- component={SplashScreen}
- />
- )}
so now its:
import React, { useEffect } from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { useSelector } from 'react-redux';
const MainStack = createStackNavigator();
const MainStackNavigator = () => {
const auth = useSelector(authSelector);
- const splash = useSelector(splashSelector);
return (
<MainStack.Navigator>
- {/* Splash */}
- {splash.isLoading && (
- <MainStack.Screen
- name="Splash"
- component={SplashScreen}
- />
- )}
{/* Authentication */}
{auth.isLoading || !auth.isLoggedIn ? (
<MainStack.Screen
name="SignIn"
component={SignInScreen}
/>
) : (
<>
{/* Home */}
<MainStack.Screen
name="Home"
component={HomeScreen}
/>
{/* Usages */}
<MainStack.Screen
name="Usages"
component={UsagesScreen}
/>
{/* Membership */}
<MainStack.Screen
name="Membership"
component={MembershipScreen}
/>
{/* other screens here */}
</>
)}
</MainStack.Navigator>
);
};
export default MainStackNavigator;
AppNavigationContainer.tsx:
add custom <SplashScreen>
component
+ if (splash.isLoading) {
+ return (
+ <SplashScreen />
+ );
+ }
so now its:
import React, { useEffect } from 'react';
import { Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import NativeSplashScreen from 'react-native-splash-screen';
import RootStackNavigator from './rootStack';
+ import { useSelector } from 'react-redux';
export const AppNavigationContainer = () => {
+ const splash = useSelector(splashSelector);
useEffect(() => {
setTimeout(() => {
NativeSplashScreen.hide();
}, 500);
}, []);
const linking = {
// todo: NotFound screen
prefixes: ['myprefix://'],
config: {
initialRouteName: 'Test',
screens: {
Test: 'test',
BenefitsGuide: 'benefitsGuide',
Main: {
initialRouteName: 'Home',
screens: {
Membership: 'membership',
Usages: 'usages',
},
},
},
},
};
+ if (splash.isLoading) {
+ return (
+ <SplashScreen />
+ );
+ }
return (
<NavigationContainer
linking={linking}
fallback={(
<Text>
fallback component here
</Text>
)}
>
<RootStackNavigator />
</NavigationContainer>
);
};
so in short:
- authentication happens in
<SplashScreen>
- early return
<SplashScreen>
before<NavigationContainer>
- render
<NavigationContainer>
when we know if the user is authenticated or not
now i can deep link to specific screens
Extra notes
sorry if this was too long. i just spent a lot of time trying to combine auth flow with deep linking. and i wanted to share my findings. and i wanted to explain it fully so there would be as little confusion as possible.
just a few things to keep in mind that i found.
'fallback' prop
in my <NavigationContainer>
i use the fallback
prop: https://reactnavigation.org/docs/navigation-container#fallback
which says:
If you have a native splash screen, please use onReady instead of fallback prop.
but the onReady
prop: https://reactnavigation.org/docs/navigation-container#onready
says:
Function which is called after the navigation container and all its children finish mounting for the first time. You can use it for:
Making sure that the ref is usable. See docs regarding initialization of the ref for more details.
Hiding your native splash screen
as i have mentioned before i also have a native splash screen. however i cant use the onReady
prop to hide it. as i have to hide it earlier to render an animation in my custom <SplashScreen>
, where my authentication happens. and i only render the <NavigationContainer>
after all of that. so here i couldt follow the documentions recommendation and instead use the fallback
prop.
react-native "Linking" module
react-native also has the Linking
module you can import: https://reactnative.dev/docs/linking
here there are useful functions such as getInitialURL()
and addEventListener()
. i havent used these at all here. they seem useful, but react-navigation handles all the routing for me. i dont know if there is something that i am missing.
from react-navigation.github.io.
@Adnan-Bacic worked for me (RN + RN for web) and was not complex to implement. I still need to refactor and searching for other builtin solutions
@huberzeljko i don't get if your suggestion addresses the same issue ? how ?
from react-navigation.github.io.
But the problem @dhirendrarathod2000 mentioned persists... right?
for example.
Let's say you got an email from netflix.
In the email, You click on the movie you wanted to see, but you are not signed in.
So you go to login screen instead of the movie screen and after login you go to the movie screen not home screen.
In your proposal, your SplashScreen
will check if you are logged in or not. You know you are not logged in, so you display the SignIn
screen because that's the flow you have in your NavigationContainer
, but then, there's no redirection. Or am I missing anything?
from react-navigation.github.io.
Return different NavigationContainer before and after authentication, but linking prop should only be added to NavigationContainer after authentication.
If pre-login flow has multiple screens, add NavigationContainer, otherwise just return a single component.
if(loading) {
return <SplashComponent />
}
if(!authenticated) {
return (
<NavitationContainer> //Do not add linking prop.
<Stack.Navigator>
<Stack.Screen
name="Landing"
component={Landing}
/>
<Stack.Screen
name="SignIn"
component={SignIn}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
const linking = {
prefixes: [...],
config: {...},
getInitialURL: async () => {
const { isAvailable } = utils().playServicesAvailability;
if (isAvailable) {
const initLink = await dynamicLinks().getInitialLink();
if (initLink) {
return initLink.url;
}
}
// As a fallback, you may want to do the default deep link handling
const url = await Linking.getInitialURL();
return url;
},
subscribe: (listener) => {
// Listen to incoming links from Firebase Dynamic Links
const unsubscribeFirebase = dynamicLinks().onLink((link) => {
listener(link.url);
});
// Listen to incoming links from deep linking
Linking.addEventListener('url', (link) => {
listener(link.url);
});
return () => {
// Clean up the event listeners
unsubscribeFirebase();
Linking.removeAllListeners('url');
};
},
};
return (
<NavigationContainer linking={linking}> //Add linking prop
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
/>
<Stack.Screen
name="Profile"
component={Profile}
/>
...
...
</Stack.Navigator>
</NavigationContainer>
from react-navigation.github.io.
I used to get the initial url by using expo-linking useURL()
hook. Then assign it to a state constant initialUrl
, and when tapping login, if initialUrl !== null
, then openURL(initialUrl )
but for some reason that stopped working. Now it opens the url in the browser.
from react-navigation.github.io.
hi there! i do not quite understand this question. i'd suggest formulating it more clearly and reaching out to one of the sources for help listed on https://reactnavigation.org/help
from react-navigation.github.io.
Any update about this issue ?!!!!
from react-navigation.github.io.
@dhirendrarathod2000 Did you find a solution?
from react-navigation.github.io.
Hi! Is there a solution using react navigation v5?
from react-navigation.github.io.
amazing @Md-Mudassir47 , thats working for me :)
from react-navigation.github.io.
Is there an implementation suitable for Auth flow? I can't render all stack.screens at once due to conditions.
<RootSiblingParent>
<NavigationContainer linking={linkingOptions}>
<RootStack.Navigator
screenOptions={{
headerShown: false,
animationEnabled: false,
}}>
{state.loading && (
<RootStack.Screen
name={'Splash'}
component={Splash}
screenOptions={{
headerShown: false,
}}
/>
)}
{state.user?.auth ? (
<RootStack.Screen name={'MainStack'}>
{() => (
<MainStackNavigator category={state.user.category} />
)}
</RootStack.Screen>
) : (
<RootStack.Screen
name={'AuthStack'}
component={AuthStackNavigator}
/>
)}
</RootStack.Navigator>
</NavigationContainer>
</RootSiblingParent>
from react-navigation.github.io.
Authstack does not work if it is conditional. The solution is to remove all conditions. However, this contradicts existing documentation.
from react-navigation.github.io.
Authstack does not work if it is conditional
Why ?
from react-navigation.github.io.
I don't know exactly why. Presumably the condition is checked as soon as the application is opened.
from react-navigation.github.io.
Hi together,
I'm a supabase user and there is a problem (but i don't know if it's really a problem) where you get the session at the second request (the first one is null). I also using the official auth flow from react-navigation and no suggested solution is working. If the app is in foreground my pushes will be redirected to the right screen.
Are there any official solutions from the react-navigation development team?
Best regards,
Andy
from react-navigation.github.io.
Hi together,
I'm a supabase user and there is a problem (but i don't know if it's really a problem) where you get the session at the second request (the first one is null). I also using the official auth flow from react-navigation and no suggested solution is working. If the app is in foreground my pushes will be redirected to the right screen.
Are there any official solutions from the react-navigation development team?
Best regards, Andy
I found a workaround to handle it, here's the solution
from react-navigation.github.io.
unable to pass params from deep link. getting undefined
when running:
npx uri-scheme open [prefix]://news/3 --android
-
NewsScreen.js
import React from 'react'; const NewsScreen = ({ route, navigation }) => { console.log(route.params); // undefined };
-
Linking.js
import LINKING_PREFIXES from 'src/shared/constants'; export const linking = { prefixes: LINKING_PREFIXES, config: { screens: { Home: { screens: { News: { path: 'news/:id?', parse: { id: id => `${id}`, }, }, }, }, NotFound: '*', }, }, };
-
Router.js
import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import {useAuth} from 'src/contexts/AuthContext'; import {Loading} from 'src/components/Loading'; import {AppStack} from './AppStack'; import {AuthStack} from './AuthStack'; import {GuestStack} from './GuestStack'; import SplashScreen from 'src/screens/guest/SplashScreen'; import linking from './Linking.js'; export const Router = () => { const {authData, loading, isFirstTime, appBooting} = useAuth(); if (loading) { return <Loading />; } const loadRoutes = () => { if (appBooting) { return <SplashScreen />; } if (isFirstTime) { return <GuestStack />; } if (!authData || !authData.name || !authData.confirmed) { return <AuthStack />; } return <AppStack />; }; return <NavigationContainer>{loadRoutes()}</NavigationContainer>; };
-
AppStack.js
import React from 'react'; import {createDrawerNavigator} from '@react-navigation/drawer'; import {SpeedNewsStack} from 'src/routes/NewsStack'; const Drawer = createDrawerNavigator(); export const AppStack = () => { return ( <Drawer.Navigator> <Drawer.Screen name="Home" component={NewsStack} /> </Drawer.Navigator> ); };
-
NewsStack.js
import React from 'react'; import {createStackNavigator} from '@react-navigation/stack'; import SpeedNewsScreen from 'src/screens/NewsScreen'; const Stack = createStackNavigator(); export const NewsStack = () => { return ( <Stack.Navigator> <Stack.Screen name="News" component={NewsScreen} /> </Stack.Navigator> ); };
from react-navigation.github.io.
I've extracted the whole linking configuration into function
function createLinking({ isAuthenticated,}: { isAuthenticated: boolean}): LinkingOptions<RootStackParamList> { ... }
This way you can do additional checks in your configuration and link to appropriate screen. I'm using React.Context
for state management, so whenever authentication state is changed, whole navigation is rerendered (this is done anyway) with new configuration and correct isAuthenticated
flag.
Let me know if someone has any issues with it, but for now it seems to work for all the test cases.
from react-navigation.github.io.
@chas-ps In general it is a good solution, but there are some caviats to note about this solution:
- The wildcard '*' will only be used as a fallback if the pattern is not found in the linking configuration. That means that the user tries to access myapp://home, he will not be directed to SignIn page. There will be an error message in the console saying Home component is not rendered
- If we call navigateInsideApp() using the onPress handler of the sign in button, React Navigation can't navigate to the desired component, as it was not rendered yet. We need no handle the deeplink after React Navigation reacts to the authentication.
- The method getInitialUrl is not called everytime the user opens a deeplink, I couldn't understand why. When I close the app and open a deeplink, getInitialUrl is called successfully. But when I do it with the app running in background, getInitialUrl is not called.
from react-navigation.github.io.
Any perfect solution?
from react-navigation.github.io.
@Adnan-Bacic @davidlevy I don't really understand the proposal. How are you going to manage the auth in the SplashScreen
without being in the sign in screen?
from react-navigation.github.io.
@Estevete you manage the auth in the SplashScreen
component. here you find out if they are logged in our not. only when you know this, then you can render the NavigationContainer
which conditionally renders screens depending if users are logged in or not.
from react-navigation.github.io.
Return different NavigationContainer before and after authentication, but linking prop should only be added to NavigationContainer after authentication.
this is not recommended, as seen here: https://reactnavigation.org/docs/getting-started/#wrapping-your-app-in-navigationcontainer
Note: In a typical React Native app, the NavigationContainer should be only used once in your app at the root. You shouldn't nest multiple NavigationContainers unless you have a specific use case for them.
i think it would be better to delay rendering the NavigationContainer until the authentication logic is done running. i made a comment higher up how i did this.
from react-navigation.github.io.
@Adnan-Bacic What I did was conditionally return different NavigationContainers. The note says we should not nest NavigationContainers i.e., one container inside another. I think those are two different things, correct me if I am wrong.
Will implement and check your solution as well when I get a chance. But this solution is working for my use case, where I need deep linking only after login. If we need deep linking both before and after login, my solution does not work.
If I give linking prop to NavigationConatiner pre-login, getInitialUrl is called on that container, therefore the state is lost and after login the new container does not receive the initialUrl. We can, of course maintain the initialUrl in state and somehow use it later, but my use case is so simple and straight-forward.
from react-navigation.github.io.
@Tharun122 fair enough. i think i have read before somewhere in the docs that there shouldt be more than 1 NavigationContainer, but i cant find anything.
the only thing i can find is from version 4: https://reactnavigation.org/docs/4.x/common-mistakes#explicitly-rendering-more-than-one-navigator
but maybe something changed from version 4 to 5, as NavigationContainer didnt exist before version 5.
from react-navigation.github.io.
My solution is a bit of a hack. I have a Screen
component that wraps all of my screens. I have added a needsAuth
prop which causes the component to check if the user is logged in. If they're not, render nothing and push a login screen. Pop login screen once auth is complete and the user can keep doing whatever they wanna do!
from react-navigation.github.io.
Has anyone found a solid solution to this? I'm putting together a solution, but it feels brittle having to take the path that was receiving on the initial deepLink and walk back through the StackParamList to find the correct screen name to navigate to.
from react-navigation.github.io.
hey there i have a solution it working for both when app is killed also when app is in foreground or background state for the app is killed in background state i am checking if the app is opened by url if then it will redirect to certasin screen following is my solution
// root navigator
const RootNavigator = () => {
const {
state: {userData},
fetchDataFromLocalStorage,
} = useContext(UserContext);
// check if user sign in already
useEffect(() => {
fetchDataFromLocalStorage({
navigation: async e => {
try {
// when app open by deep link it will redirect to our screen
//if link is attach to some screen
// a hot fix bacause we are using authentication flow
//in which we are conditional changing stack so link try to find screen but
// we are still cheking if user logged in or not
let initURL = await Linking.getInitialURL();
console.log('respose of get inital url is', initURL);
if (initURL) {
// if url link then navigate to that linked screen
await Linking.openURL(initURL);
}
SplashScreen.hide();
} catch (error) {
console.log('unable to get url', error);
}
},
});
}, []);
return (
<Stack.Navigator>
<Stack.Screen
name={userData ? 'HomeStack' : 'AuthStack'}
component={userData ? HomeStack : AuthStack}
options={{headerShown: false}}
/>
</Stack.Navigator>
);
};
// main navigator
export default function AppNavigator() {
// configuration for every screen with in our app attaching url
// we attach default / with auth stack and /home with home stack
//and /home/notifaction to notification screen
const config = {
screens: {
AuthStack: '',
HomeStack: {
path: 'home',
screens: {
NotificationsScreen: 'notification',
},
},
},
};
//link for opening the app base url for opeoning the eg if we type
// 'https://pupspotter.com' it will redirect to our app playstore
const linking = {
prefixes: ['pupspotter://', 'https://pupspotter.com'],
config,
};
return (
<NavigationContainer
linking={linking}
fallback={Loading...}>
<KeyboardAvoidingView
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 35}
style={GlobalStyles.mainContainer}>
);
}
and this is my fetchDataFromLocalStorage function
/ fetch data fom local storage
const fetchDataFromLocalStorage =
dispatch =>
async ({setLoading = () => {}, navigation = () => {}}) => {
try {
// setLoading(true);
// checking token is there in async
let userLocalStorageData = await AsyncStorage.getItem(ASYNC_USER_KEY);
console.debug('REPONSE[FETCH_DATA_FROM_LOCAL_STORAGE]', userLocalStorageData);
if (userLocalStorageData) {
// parsing async data
userLocalStorageData = JSON.parse(userLocalStorageData);
dispatch({
type: SAVE_USER_DATA,
payload: userLocalStorageData,
});
console.log('calling get user by id in fetch data drom local');
let response = await API.get(/user/${userLocalStorageData.PK}
);
console.debug('REPONSE[GET_USER_DATA_BY_ID]', response);
let data = response?.data?.data;
// saving on state
dispatch({
type: SAVE_USER_DATA,
payload: data,
});
}
setLoading(false);
navigation();
} catch (error) {
console.debug('ERROR[FETCH_DATA_FROM_LOCAL_STORAGE]', error);
//error
setLoading(false);
Toast.show('Unable to fetch user data try again later', Toast.LONG);
// navigation(error);
console.debug('ERROR[FETCH_DATA_FROM_LOCAL_STORAGE]', error);
// crashLogs('actions.js', 'updateUserLastSeen', error);
}
};
by the way using context api for state managment
from react-navigation.github.io.
Implement getInitialURL
properly.
...,
config: deepLinkConfigs,
async getInitialURL() {
const url = decodeURI((await Linking.getInitialURL()) ?? '');
if (url) return url;
// Check if there is an initial firebase notification
const message = await messaging().getInitialNotification();
// Get deep link from data
// if this is undefined, the app will open the default/home page
return message?.data?.link ?? '';
},
...
from react-navigation.github.io.
I solve this problem by using MMKV(similar AsyncStorage).
- Store 'openedDeepLinkUrl' to storage when initialNotification exists.
const linking = {
prefixes: [...],
config: deepLinksConfig,
async getInitialURL() {
// Check if app was opened from a deep link
const url = await Linking.getInitialURL();
if (url != null) return url;
const initialNotification = await notifee.getInitialNotification();
if (!initialNotification) return null;
const {data} = initialNotification.notification;
if (!data || !data.link) return null;
storage.set('openedDeepLinkUrl', data.link as string);
},
};
- If the app is loaded and logged in(Usually in App.tsx), get 'openedDeepLinkUrl' from the storage and openURL.
(finally, delete the storage key)
// ...set app loading and login status true
const openedDeepLinkUrl = storage.getString('openedDeepLinkUrl');
if (openedDeepLinkUrl) {
await Linking.openURL(openedDeepLinkUrl);
storage.delete('openedDeepLinkUrl');
}
from react-navigation.github.io.
Related Issues (20)
- Error 404 when following Nesting Navigator hyperlink to expo Snack
- Stack navigator for each tab code example warning HOT 1
- Inconsistent TypeScript behavior with optional arguments in useNavigation
- Request for a sandbox environment for React Navigation
- Found screens with the same name nested inside one another HOT 2
- React Navigation logo has multiple control points
- In "Help" page witgh presentation set value to "transparentModal", I want push one new Page of name "Profile", How shuld i do?
- [email protected] -> Compiling JS failed .... invalid expression Buffer
- Improve/Update React-Navigation SVGs
- Remove 'make me a sandwich' references from documentation .mov
- Most of the react navigation examples on snack are not working HOT 2
- Use Deep Link, occur error
- React-native physical iOS devices not showing images
- NavigationContainer: initialRouteName: Issue with TypeScript
- Group Component Information HOT 2
- Nested navigation Snack Expample NOT FOUND
- Missing import on multiple-drawers
- Modal example issue - `RNSScreen` must be a function HOT 1
- Broken outbound URL HOT 1
- Remove mention of deprecated 'jest-native' in docs
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-navigation.github.io.