Skip to content

Commit

Permalink
feat: associate path with the route it opens when deep linking (#9384)
Browse files Browse the repository at this point in the history
This commit adds a new optional property on the `route` object called `path`.
This property will be added if the screen was opened from a deep link.

Having this property helps with few things:

- Preserve the URL when the path was unmatched, e.g. 404 routes
- Expose the path to the user so they could handle it manually if needed, e.g. open in a webview
- Avoid changing URL if state to path doesn't match current path, e.g. if orders of params change

Fixes #9102
  • Loading branch information
satya164 committed Mar 5, 2021
1 parent 585ae7d commit 86e64fd
Show file tree
Hide file tree
Showing 21 changed files with 532 additions and 104 deletions.
3 changes: 2 additions & 1 deletion example/src/Screens/NotFound.tsx
Expand Up @@ -4,11 +4,12 @@ import { Button } from 'react-native-paper';
import type { StackScreenProps } from '@react-navigation/stack';

const NotFoundScreen = ({
route,
navigation,
}: StackScreenProps<{ Home: undefined }>) => {
return (
<View style={styles.container}>
<Text style={styles.title}>404 Not Found</Text>
<Text style={styles.title}>404 Not Found ({route.path})</Text>
<Button
mode="contained"
onPress={() => navigation.navigate('Home')}
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Expand Up @@ -47,6 +47,7 @@
"@types/react": "^16.9.53",
"@types/react-is": "^16.7.1",
"del-cli": "^3.0.1",
"immer": "^8.0.1",
"react": "~16.13.1",
"react-native-builder-bob": "^0.17.1",
"react-test-renderer": "~16.13.1",
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/BaseNavigationContainer.tsx
Expand Up @@ -21,6 +21,7 @@ import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState';
import checkSerializable from './checkSerializable';
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
import findFocusedRoute from './findFocusedRoute';
import type {
NavigationContainerEventMap,
NavigationContainerRef,
Expand Down Expand Up @@ -185,14 +186,15 @@ const BaseNavigationContainer = React.forwardRef(
}, [keyedListeners.getState]);

const getCurrentRoute = React.useCallback(() => {
let state = getRootState();
if (state === undefined) {
const state = getRootState();

if (state == null) {
return undefined;
}
while (state.routes[state.index].state !== undefined) {
state = state.routes[state.index].state as NavigationState;
}
return state.routes[state.index];

const route = findFocusedRoute(state);

return route as Route<string> | undefined;
}, [getRootState]);

const emitter = useEventEmitter<NavigationContainerEventMap>();
Expand Down
39 changes: 33 additions & 6 deletions packages/core/src/__tests__/getActionFromState.test.tsx
Expand Up @@ -15,6 +15,7 @@ it('gets navigate action from state', () => {
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
Expand All @@ -35,6 +36,7 @@ it('gets navigate action from state', () => {
author: 'jane',
},
screen: 'qux',
path: '/foo/bar',
initial: true,
},
screen: 'bar',
Expand All @@ -51,6 +53,7 @@ it('gets navigate action from state for top-level screen', () => {
{
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
],
};
Expand All @@ -59,6 +62,7 @@ it('gets navigate action from state for top-level screen', () => {
payload: {
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
Expand All @@ -80,6 +84,7 @@ it('gets reset action from state with 1 route with key at root', () => {
key: 'test',
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
Expand All @@ -102,7 +107,12 @@ it('gets reset action from state with 1 route with key at root', () => {
name: 'bar',
state: {
routes: [
{ key: 'test', name: 'qux', params: { author: 'jane' } },
{
key: 'test',
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
},
Expand All @@ -125,6 +135,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
Expand All @@ -139,6 +150,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
Expand Down Expand Up @@ -197,6 +209,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
name: 'bar',
key: 'test',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
Expand All @@ -219,6 +232,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
name: 'bar',
key: 'test',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
Expand All @@ -236,6 +250,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
],
};
Expand All @@ -251,6 +266,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
payload: {
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
Expand All @@ -267,6 +283,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
{
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'baz' },
],
Expand All @@ -283,6 +300,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
payload: {
name: 'bar',
params: { author: 'jane' },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
Expand All @@ -303,7 +321,7 @@ it('gets navigate action from state with 2 screens', () => {
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
{ name: 'quz', path: '/foo/bar' },
],
},
},
Expand All @@ -328,7 +346,7 @@ it('gets navigate action from state with 2 screens', () => {
author: 'jane',
},
},
{ name: 'quz' },
{ name: 'quz', path: '/foo/bar' },
],
},
},
Expand All @@ -353,6 +371,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'quz' },
],
Expand All @@ -376,6 +395,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
params: {
author: 'jane',
},
path: '/foo/bar',
},
},
},
Expand Down Expand Up @@ -450,6 +470,7 @@ it('gets navigate action from state with config', () => {
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
],
},
Expand Down Expand Up @@ -483,6 +504,7 @@ it('gets navigate action from state with config', () => {
author: 'jane',
},
screen: 'qux',
path: '/foo/bar',
initial: true,
},
screen: 'bar',
Expand All @@ -499,6 +521,7 @@ it('gets navigate action from state for top-level screen with config', () => {
{
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
],
};
Expand All @@ -519,6 +542,7 @@ it('gets navigate action from state for top-level screen with config', () => {
payload: {
name: 'foo',
params: { answer: 42 },
path: '/foo/bar',
},
type: 'NAVIGATE',
});
Expand All @@ -539,7 +563,7 @@ it('gets navigate action from state with 2 screens including initial route and w
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
{ name: 'quz', path: '/foo/bar' },
],
},
},
Expand Down Expand Up @@ -571,6 +595,7 @@ it('gets navigate action from state with 2 screens including initial route and w
params: {
screen: 'quz',
initial: false,
path: '/foo/bar',
},
},
},
Expand All @@ -593,7 +618,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
name: 'qux',
params: { author: 'jane' },
},
{ name: 'quz' },
{ name: 'quz', path: '/foo/bar' },
],
},
},
Expand Down Expand Up @@ -631,7 +656,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
author: 'jane',
},
},
{ name: 'quz' },
{ name: 'quz', path: '/foo/bar' },
],
},
},
Expand Down Expand Up @@ -856,6 +881,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
{
name: 'qux',
params: { author: 'jane' },
path: '/foo/bar',
},
{ name: 'quz' },
],
Expand Down Expand Up @@ -889,6 +915,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
params: {
screen: 'qux',
initial: false,
path: '/foo/bar',
params: {
author: 'jane',
},
Expand Down
18 changes: 11 additions & 7 deletions packages/core/src/__tests__/getPathFromState.test.tsx
Expand Up @@ -82,7 +82,11 @@ it('converts state to path string with config', () => {
routes: [
{
name: 'Baz',
params: { author: 'Jane', valid: true, id: 10 },
params: {
author: 'Jane',
id: 10,
valid: true,
},
},
],
},
Expand Down Expand Up @@ -186,9 +190,9 @@ it('handles state with config with nested screens', () => {
{
name: 'Baz',
params: {
answer: '42',
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
Expand Down Expand Up @@ -265,9 +269,9 @@ it('handles state with config with nested screens and exact', () => {
{
name: 'Baz',
params: {
answer: '42',
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
Expand Down Expand Up @@ -333,9 +337,9 @@ it('handles state with config with nested screens and unused configs', () => {
{
name: 'Baz',
params: {
answer: '42',
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
Expand Down Expand Up @@ -399,9 +403,9 @@ it('handles state with config with nested screens and unused configs with exact'
{
name: 'Baz',
params: {
answer: '42',
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
Expand Down Expand Up @@ -476,9 +480,9 @@ it('handles nested object with stringify in it', () => {
{
name: 'Bis',
params: {
answer: '42',
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
Expand Down Expand Up @@ -558,9 +562,9 @@ it('handles nested object with stringify in it with exact', () => {
{
name: 'Bis',
params: {
answer: '42',
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
Expand Down

0 comments on commit 86e64fd

Please sign in to comment.