Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: manage unhandled deep link on authentication #10939

Closed

Conversation

kacperkapusciak
Copy link
Member

@kacperkapusciak kacperkapusciak commented Oct 17, 2022

Motivation

This PR implements API for handling deep links with conditional rendering described in detail by @satya164 in Handle deep link + auth in React Navigation.

Demo

Before

Default behavior

Screen.Recording.2022-10-20.at.16.42.05.mov

After

With the use of useLinkingOnConditionalRender hook

with.useLinkingOnConditionalRender.mov

Test plan

Invoke deep link in Expo Go on iOS:
xcrun simctl openurl booted exp://127.0.0.1:19000/--/profile

Invoke deep link in Expo Go on Android

adb shell am start -W -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/profile" host.exp.exponent

Code example
import {
  NavigationContainer,
  useLinkingOnConditionalRender,
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createURL } from 'expo-linking';
import React from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';

const HomeScreen = ({ navigation }: any) => (
  <View style={styles.container}>
    <Text style={{ ...styles.text, ...{ color: 'teal' } }}>Home Screen</Text>
    <Button
      onPress={() => navigation.navigate('Profile')}
      title="Go to profile"
    />
  </View>
);

const DetailsScreen = ({ route }: any) => {
  const {
    params: { message },
  } = route;

  return (
    <View style={styles.container}>
      {message && <Text style={styles.text}>Message: {message}</Text>}
    </View>
  );
};

const ProfileScreen = ({ navigation }: any) => (
  <View style={styles.container}>
    <Text style={{ ...styles.text, ...{ color: 'indianred' } }}>
      Profile Screen
    </Text>
    <Button onPress={() => navigation.popTo('Home')} title="Go back to home" />
  </View>
);

const SignInScreen = ({ signIn }: any) => {
  const scheduleNext = useLinkingOnConditionalRender();

  return (
    <View style={styles.container}>
      <Text style={styles.text}>SignIn Screen</Text>
      <Button
        onPress={() => {
          scheduleNext();
          signIn();
        }}
        title="Sign In"
      />
    </View>
  );
};

const Stack = createStackNavigator();

const config = {
  screens: {
    Home: 'home',
    Details: {
      path: 'details/:message',
      parse: {
        message: (message: string) => `${message}`,
      },
    },
    Profile: 'profile',
  },
};

const linking = {
  prefixes: [createURL('/')],
  config,
  // async getInitialURL() {
  //   return `${createURL('/')}profile`;
  // },
};

export default function App() {
  const [isSignedIn, setSignedIn] = React.useState(false);
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        {isSignedIn ? (
          <Stack.Group
            screenOptions={{
              headerRight: () => (
                <Button onPress={() => setSignedIn(false)} title="Sign Out" />
              ),
            }}
          >
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Details" component={DetailsScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Group>
        ) : (
          <Stack.Screen
            name="SignIn"
            options={{
              animationTypeForReplace: !isSignedIn ? 'pop' : 'push',
            }}
          >
            {() => <SignInScreen signIn={() => setSignedIn(true)} />}
          </Stack.Screen>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

satya164 and others added 18 commits September 30, 2022 19:18
BREAKING CHANGE: Previously, you could specify a route `key` to navigate to, e.g.
`navigation.navigate({ key: 'someuniquekey' })`. It's problematic since `key` is an internal
implementation detail and created by the library internally - which makes it weird to use. None
of the other actions support such usage either.

In addition, we have already added a better API (`getId`) which can be used for similar use
cases - and gives users full control since they provide the ID and it's not autogenerated by the
library.

So this change removes the `key` option from the `navigate` action.
`navigate`

BREAKING CHANGE: Previously, `navigate` method navigated back if the screen
already exists in the stack. I have seen many people get confused by this
behavior. This behavior is also used for sending params to a previous
screen in the documentation. However, it's also problematic since it could
either push or pop the screens based on the scenario.

This removes the going back behavior from `navigate` and adds a new method
`popTo` to go back to a specific screen in the stack.

The methods now behave as follows:

- `navigate(screenName)` will stay on the current screen if the screen is
already focused, otherwise push a new screen to the stack.
- `popTo(screenName)` will go back to the screen if it exists in the stack,
otherwise pop the current screen and add this screen to the stack.
- To achieve the previous behavior with `navigate`, you can use the `getId`
prop in which case it'll go to the screen with the matching ID and push or
pop screens accordingly.
BREAKING CHANGE: Previously, the `onReady` prop and `navigationRef.isReady()` work slightly
differently. The
`onReady` callback fired when `NavigationContainer` finishes mounting and deep links is resolved.
The `navigationRef.isReady()` method additionally checks if there are any navigators rendered - which may not be `true` if the user is rendering their navigators conditionally inside a
`NavigationContainer`.

This changes `onReady` to work similar to `navigationRef.isReady()`. The `onReady` callback will now fire only when there are navigators rendered - reflecting the value of
`navigationRef.isReady()`.
BREAKING CHANGE: Previously, you could specify a route `key` to navigate to, e.g.
`navigation.navigate({ key: 'someuniquekey' })`. It's problematic since `key` is an internal
implementation detail and created by the library internally - which makes it weird to use. None
of the other actions support such usage either.

In addition, we have already added a better API (`getId`) which can be used for similar use
cases - and gives users full control since they provide the ID and it's not autogenerated by the
library.

So this change removes the `key` option from the `navigate` action.
`navigate`

BREAKING CHANGE: Previously, `navigate` method navigated back if the screen
already exists in the stack. I have seen many people get confused by this
behavior. This behavior is also used for sending params to a previous
screen in the documentation. However, it's also problematic since it could
either push or pop the screens based on the scenario.

This removes the going back behavior from `navigate` and adds a new method
`popTo` to go back to a specific screen in the stack.

The methods now behave as follows:

- `navigate(screenName)` will stay on the current screen if the screen is
already focused, otherwise push a new screen to the stack.
- `popTo(screenName)` will go back to the screen if it exists in the stack,
otherwise pop the current screen and add this screen to the stack.
- To achieve the previous behavior with `navigate`, you can use the `getId`
prop in which case it'll go to the screen with the matching ID and push or
pop screens accordingly.
BREAKING CHANGE: Previously, the `onReady` prop and `navigationRef.isReady()` work slightly
differently. The
`onReady` callback fired when `NavigationContainer` finishes mounting and deep links is resolved.
The `navigationRef.isReady()` method additionally checks if there are any navigators rendered - which may not be `true` if the user is rendering their navigators conditionally inside a
`NavigationContainer`.

This changes `onReady` to work similar to `navigationRef.isReady()`. The `onReady` callback will now fire only when there are navigators rendered - reflecting the value of
`navigationRef.isReady()`.
PR removes deprecation warnings introduced in 8e0ae5f
Due to backward compatibility reasons, React Navigation 5 and 6 support navigating to a screen in a child navigator with `navigation.navigate(ScreenName)` syntax. But this is problematic with the new architecture - it only works if the navigator is already mounted, doesn't work with TypeScript, etc. That's why there's a special API to navigate to a nested screen (`navigation.navigate(ParentScreenName, { screen: ScreenName })`).

This drops this behavior and adds a prop to explicitly enable it to make it easier to migrate. This prop will be removed in the next major.
@github-actions
Copy link

The Expo app for the example from this branch is ready!

expo.dev/@react-navigation/react-navigation-example?release-channel=pr-10939

@kacperkapusciak kacperkapusciak changed the title feat: handle deep link + auth feat: manage unhandled deep link on authentication Oct 20, 2022
@kacperkapusciak kacperkapusciak marked this pull request as ready for review October 20, 2022 15:19
satya164 and others added 9 commits November 16, 2022 09:06
BREAKING CHANGE: Previously, you could specify a route `key` to navigate to, e.g.
`navigation.navigate({ key: 'someuniquekey' })`. It's problematic since `key` is an internal
implementation detail and created by the library internally - which makes it weird to use. None
of the other actions support such usage either.

In addition, we have already added a better API (`getId`) which can be used for similar use
cases - and gives users full control since they provide the ID and it's not autogenerated by the
library.

So this change removes the `key` option from the `navigate` action.
`navigate`

BREAKING CHANGE: Previously, `navigate` method navigated back if the screen
already exists in the stack. I have seen many people get confused by this
behavior. This behavior is also used for sending params to a previous
screen in the documentation. However, it's also problematic since it could
either push or pop the screens based on the scenario.

This removes the going back behavior from `navigate` and adds a new method
`popTo` to go back to a specific screen in the stack.

The methods now behave as follows:

- `navigate(screenName)` will stay on the current screen if the screen is
already focused, otherwise push a new screen to the stack.
- `popTo(screenName)` will go back to the screen if it exists in the stack,
otherwise pop the current screen and add this screen to the stack.
- To achieve the previous behavior with `navigate`, you can use the `getId`
prop in which case it'll go to the screen with the matching ID and push or
pop screens accordingly.
BREAKING CHANGE: Previously, the `onReady` prop and `navigationRef.isReady()` work slightly
differently. The
`onReady` callback fired when `NavigationContainer` finishes mounting and deep links is resolved.
The `navigationRef.isReady()` method additionally checks if there are any navigators rendered - which may not be `true` if the user is rendering their navigators conditionally inside a
`NavigationContainer`.

This changes `onReady` to work similar to `navigationRef.isReady()`. The `onReady` callback will now fire only when there are navigators rendered - reflecting the value of
`navigationRef.isReady()`.
PR removes deprecation warnings introduced in 8e0ae5f
Due to backward compatibility reasons, React Navigation 5 and 6 support navigating to a screen in a child navigator with `navigation.navigate(ScreenName)` syntax. But this is problematic with the new architecture - it only works if the navigator is already mounted, doesn't work with TypeScript, etc. That's why there's a special API to navigate to a nested screen (`navigation.navigate(ParentScreenName, { screen: ScreenName })`).

This drops this behavior and adds a prop to explicitly enable it to make it easier to migrate. This prop will be removed in the next major.
@codecov-commenter
Copy link

Codecov Report

❗ No coverage uploaded for pull request base (7.x@9ee1317). Click here to learn what that means.
Patch has no changes to coverable lines.

Additional details and impacted files
@@          Coverage Diff           @@
##             7.x   #10939   +/-   ##
======================================
  Coverage       ?   76.07%           
======================================
  Files          ?      172           
  Lines          ?     5175           
  Branches       ?     1996           
======================================
  Hits           ?     3937           
  Misses         ?     1198           
  Partials       ?       40           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@satya164
Copy link
Member

/rebase

@satya164 satya164 force-pushed the 7.x branch 3 times, most recently from 1b0a943 to 89cde12 Compare November 18, 2022 08:01
@satya164 satya164 changed the base branch from 7.x to 7.x-old November 28, 2022 10:02
osdnk added a commit that referenced this pull request Sep 17, 2023
@osdnk osdnk mentioned this pull request Oct 1, 2023
satya164 pushed a commit that referenced this pull request Oct 19, 2023
…tc. (#11602)

This code is mostly moved changes from #10939.
Additionally, I propose a different name and add ability to handle the
login in nested navigators

**Motivation**

This PR implements API for handling deep links with conditional
rendering. Refer to [Handle deep link + auth in React
Navigation](https://gist.github.com/satya164/af3a6f25c03dfbae9ca72ede19d170bc).
**Test plan**

To check, open `index.ts` and change the flag `LINKING_EXAMPLE = true`

Then, use `xcrun simctl openurl booted
exp://127.0.0.1:19000/--/outer/profile`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@osdnk osdnk closed this Dec 28, 2023
@satya164 satya164 deleted the @kacperkapusciak/handle-deep-linking-after-auth branch March 12, 2024 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants