Coder Social home page Coder Social logo

Comments (46)

dhirendrarathod2000 avatar dhirendrarathod2000 commented on April 28, 2024 32

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.

chas-ps avatar chas-ps commented on April 28, 2024 22

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:

  1. Add a new state variable to the App component named initialDeepLink
  2. Register the NavigationContainer's getInitialUrl() prop added in v5.8.x (documented here)
  3. Inside that method, grab the initial deep link by the default method await Linking.getInitialUrl() (or w/e works for your app)
  4. Update the new initialDeepLink state variable (causing single re-render)
  5. Setup a React.createContext() object specifically for passing the initial deep link, something like InitialDeepLinkContext
  6. Register the <InitialDeepLinkContext.Provider value={initialDeepLink}> in the App component as a parent of the RootNavigator (in case of example above)
  7. Access the initial deep link from the Login screen using React context: const initialDeepLink = useContext(InitialDeepLinkContext)
  8. Check if the initialDeepLink is null or empty, if so, define a default deep link to navigate to (e.g. someapp://home), something like const initialOrDefaultDeepLink = initialDeepLink || 'someapp://home';
  9. Use the newer useLinkingProps hook from react-navigation to create an onPress handler for navigating via deep link from a component: const { onPress: navigateInsideApp } = useLinkingProps({ to: initialOrDefaultDeepLink }}
  10. When the user presses your login button, call navigateInsideApp() to navigate to the default screen, or continue following an initial deep link
  11. (?) Somehow clean-up the initialDeepLink state to null 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.

Lokendra-rawat avatar Lokendra-rawat commented on April 28, 2024 21

Anything for react navigation 5?

from react-navigation.github.io.

dhirendrarathod2000 avatar dhirendrarathod2000 commented on April 28, 2024 17

@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.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024 11

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.

  1. Navigation happens automatically
  2. 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.

brentvatne avatar brentvatne commented on April 28, 2024 10

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.

Md-Mudassir47 avatar Md-Mudassir47 commented on April 28, 2024 9

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.

Aaronhuu avatar Aaronhuu commented on April 28, 2024 3

Tried all the solutions above, this works for me finally:

Create linking config to NavigationContainer, pass linking as a prop.

  1. When open the app with deep link, store the deeplink using Linking (react-native) and useEffect Hooks (I use redux)
  2. 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.

ludu12 avatar ludu12 commented on April 28, 2024 2

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.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024 2

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.

AnthonyDugarte avatar AnthonyDugarte commented on April 28, 2024 1

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.

huttotw avatar huttotw commented on April 28, 2024 1

@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.

satya164 avatar satya164 commented on April 28, 2024 1

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.

christophemenager avatar christophemenager commented on April 28, 2024 1

Quick summary of what I did, maybe it could help others.

I followed a different path from what @chas-ps tried (that probably works) :

  1. While I am doing the authentication call to my server (is_authentication is null), I keep the bootsplash shown
  2. 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)
  3. When my authentication call is over, is_authentication is true or false, and then I can render the NavigationContainer
  4. 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.

muiruri avatar muiruri commented on April 28, 2024 1

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.

christophemenager avatar christophemenager commented on April 28, 2024 1

@muiruri smart workaround ! :)

from react-navigation.github.io.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024 1

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:

  1. reduce amount of repetitive boilerplate when there are multiple screens
  2. most imports which are specific to my project are removed
  3. 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:

  1. shows a native splash screen with react-native-splash-screen
  2. show my custom <SplashScreen> component
  3. show screen depending on auth status

when i deep like now it goes like this:

  1. shows a native splash screen with react-native-splash-screen
  2. 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:

  1. authentication happens in <SplashScreen>
  2. early return <SplashScreen> before <NavigationContainer>
  3. 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.

davidlevy avatar davidlevy commented on April 28, 2024 1

@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.

Estevete avatar Estevete commented on April 28, 2024 1

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.

Tharun122 avatar Tharun122 commented on April 28, 2024 1

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.

Estevete avatar Estevete commented on April 28, 2024 1

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.

brentvatne avatar brentvatne commented on April 28, 2024

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.

codiri avatar codiri commented on April 28, 2024

Any update about this issue ?!!!!

from react-navigation.github.io.

mohamed-ikram avatar mohamed-ikram commented on April 28, 2024

@dhirendrarathod2000 Did you find a solution?

from react-navigation.github.io.

sclavijo93 avatar sclavijo93 commented on April 28, 2024

Hi! Is there a solution using react navigation v5?

from react-navigation.github.io.

Jonovono avatar Jonovono commented on April 28, 2024

amazing @Md-Mudassir47 , thats working for me :)

from react-navigation.github.io.

mehmetcansahin avatar mehmetcansahin commented on April 28, 2024

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.

mehmetcansahin avatar mehmetcansahin commented on April 28, 2024

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.

christophemenager avatar christophemenager commented on April 28, 2024

Authstack does not work if it is conditional

Why ?

from react-navigation.github.io.

mehmetcansahin avatar mehmetcansahin commented on April 28, 2024

I don't know exactly why. Presumably the condition is checked as soon as the application is opened.

from react-navigation.github.io.

megacherry avatar megacherry commented on April 28, 2024

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.

Md-Mudassir47 avatar Md-Mudassir47 commented on April 28, 2024

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.

pokhiii avatar pokhiii commented on April 28, 2024

unable to pass params from deep link. getting undefined when running:

npx uri-scheme open [prefix]://news/3 --android

  1. NewsScreen.js

    import React from 'react';
    
    const NewsScreen = ({ route, navigation }) => {
       console.log(route.params); // undefined
    };
  2. 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: '*',
          },
       },
    };
  3. 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>;
    };
  4. 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>
       );
    };
  5. 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.

huberzeljko avatar huberzeljko commented on April 28, 2024

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.

guto-marzagao avatar guto-marzagao commented on April 28, 2024

@chas-ps In general it is a good solution, but there are some caviats to note about this solution:

  1. 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
  2. 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.
  3. 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.

SohelIslamImran avatar SohelIslamImran commented on April 28, 2024

Any perfect solution?

from react-navigation.github.io.

Estevete avatar Estevete commented on April 28, 2024

@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.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024

@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.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024

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.

Tharun122 avatar Tharun122 commented on April 28, 2024

@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.

Adnan-Bacic avatar Adnan-Bacic commented on April 28, 2024

@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.

julian-hecker avatar julian-hecker commented on April 28, 2024

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.

airowe avatar airowe commented on April 28, 2024

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.

ahmedShaheer3 avatar ahmedShaheer3 commented on April 28, 2024

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.

vemarav avatar vemarav commented on April 28, 2024

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.

eunbae0 avatar eunbae0 commented on April 28, 2024

I solve this problem by using MMKV(similar AsyncStorage).

  1. 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);
  },
};
  1. 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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.