Skip to content

Commit 86e64fd

Browse files
authored
feat: associate path with the route it opens when deep linking (react-navigation#9384)
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 react-navigation#9102
1 parent 585ae7d commit 86e64fd

21 files changed

+532
-104
lines changed

example/src/Screens/NotFound.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { Button } from 'react-native-paper';
44
import type { StackScreenProps } from '@react-navigation/stack';
55

66
const NotFoundScreen = ({
7+
route,
78
navigation,
89
}: StackScreenProps<{ Home: undefined }>) => {
910
return (
1011
<View style={styles.container}>
11-
<Text style={styles.title}>404 Not Found</Text>
12+
<Text style={styles.title}>404 Not Found ({route.path})</Text>
1213
<Button
1314
mode="contained"
1415
onPress={() => navigation.navigate('Home')}

packages/core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@types/react": "^16.9.53",
4848
"@types/react-is": "^16.7.1",
4949
"del-cli": "^3.0.1",
50+
"immer": "^8.0.1",
5051
"react": "~16.13.1",
5152
"react-native-builder-bob": "^0.17.1",
5253
"react-test-renderer": "~16.13.1",

packages/core/src/BaseNavigationContainer.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import useEventEmitter from './useEventEmitter';
2121
import useSyncState from './useSyncState';
2222
import checkSerializable from './checkSerializable';
2323
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
24+
import findFocusedRoute from './findFocusedRoute';
2425
import type {
2526
NavigationContainerEventMap,
2627
NavigationContainerRef,
@@ -185,14 +186,15 @@ const BaseNavigationContainer = React.forwardRef(
185186
}, [keyedListeners.getState]);
186187

187188
const getCurrentRoute = React.useCallback(() => {
188-
let state = getRootState();
189-
if (state === undefined) {
189+
const state = getRootState();
190+
191+
if (state == null) {
190192
return undefined;
191193
}
192-
while (state.routes[state.index].state !== undefined) {
193-
state = state.routes[state.index].state as NavigationState;
194-
}
195-
return state.routes[state.index];
194+
195+
const route = findFocusedRoute(state);
196+
197+
return route as Route<string> | undefined;
196198
}, [getRootState]);
197199

198200
const emitter = useEventEmitter<NavigationContainerEventMap>();

packages/core/src/__tests__/getActionFromState.test.tsx

+33-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ it('gets navigate action from state', () => {
1515
{
1616
name: 'qux',
1717
params: { author: 'jane' },
18+
path: '/foo/bar',
1819
},
1920
],
2021
},
@@ -35,6 +36,7 @@ it('gets navigate action from state', () => {
3536
author: 'jane',
3637
},
3738
screen: 'qux',
39+
path: '/foo/bar',
3840
initial: true,
3941
},
4042
screen: 'bar',
@@ -51,6 +53,7 @@ it('gets navigate action from state for top-level screen', () => {
5153
{
5254
name: 'foo',
5355
params: { answer: 42 },
56+
path: '/foo/bar',
5457
},
5558
],
5659
};
@@ -59,6 +62,7 @@ it('gets navigate action from state for top-level screen', () => {
5962
payload: {
6063
name: 'foo',
6164
params: { answer: 42 },
65+
path: '/foo/bar',
6266
},
6367
type: 'NAVIGATE',
6468
});
@@ -80,6 +84,7 @@ it('gets reset action from state with 1 route with key at root', () => {
8084
key: 'test',
8185
name: 'qux',
8286
params: { author: 'jane' },
87+
path: '/foo/bar',
8388
},
8489
],
8590
},
@@ -102,7 +107,12 @@ it('gets reset action from state with 1 route with key at root', () => {
102107
name: 'bar',
103108
state: {
104109
routes: [
105-
{ key: 'test', name: 'qux', params: { author: 'jane' } },
110+
{
111+
key: 'test',
112+
name: 'qux',
113+
params: { author: 'jane' },
114+
path: '/foo/bar',
115+
},
106116
],
107117
},
108118
},
@@ -125,6 +135,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
125135
{
126136
name: 'bar',
127137
params: { author: 'jane' },
138+
path: '/foo/bar',
128139
},
129140
],
130141
};
@@ -139,6 +150,7 @@ it('gets reset action from state for top-level screen with 2 screens', () => {
139150
{
140151
name: 'bar',
141152
params: { author: 'jane' },
153+
path: '/foo/bar',
142154
},
143155
],
144156
},
@@ -197,6 +209,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
197209
name: 'bar',
198210
key: 'test',
199211
params: { author: 'jane' },
212+
path: '/foo/bar',
200213
},
201214
],
202215
};
@@ -219,6 +232,7 @@ it('gets reset action from state for top-level screen with 2 screens with config
219232
name: 'bar',
220233
key: 'test',
221234
params: { author: 'jane' },
235+
path: '/foo/bar',
222236
},
223237
],
224238
},
@@ -236,6 +250,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
236250
{
237251
name: 'bar',
238252
params: { author: 'jane' },
253+
path: '/foo/bar',
239254
},
240255
],
241256
};
@@ -251,6 +266,7 @@ it('gets navigate action from state for top-level screen with 2 screens with con
251266
payload: {
252267
name: 'bar',
253268
params: { author: 'jane' },
269+
path: '/foo/bar',
254270
},
255271
type: 'NAVIGATE',
256272
});
@@ -267,6 +283,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
267283
{
268284
name: 'bar',
269285
params: { author: 'jane' },
286+
path: '/foo/bar',
270287
},
271288
{ name: 'baz' },
272289
],
@@ -283,6 +300,7 @@ it('gets navigate action from state for top-level screen with more than 2 screen
283300
payload: {
284301
name: 'bar',
285302
params: { author: 'jane' },
303+
path: '/foo/bar',
286304
},
287305
type: 'NAVIGATE',
288306
});
@@ -303,7 +321,7 @@ it('gets navigate action from state with 2 screens', () => {
303321
name: 'qux',
304322
params: { author: 'jane' },
305323
},
306-
{ name: 'quz' },
324+
{ name: 'quz', path: '/foo/bar' },
307325
],
308326
},
309327
},
@@ -328,7 +346,7 @@ it('gets navigate action from state with 2 screens', () => {
328346
author: 'jane',
329347
},
330348
},
331-
{ name: 'quz' },
349+
{ name: 'quz', path: '/foo/bar' },
332350
],
333351
},
334352
},
@@ -353,6 +371,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
353371
{
354372
name: 'qux',
355373
params: { author: 'jane' },
374+
path: '/foo/bar',
356375
},
357376
{ name: 'quz' },
358377
],
@@ -376,6 +395,7 @@ it('gets navigate action from state with 2 screens with lower index', () => {
376395
params: {
377396
author: 'jane',
378397
},
398+
path: '/foo/bar',
379399
},
380400
},
381401
},
@@ -450,6 +470,7 @@ it('gets navigate action from state with config', () => {
450470
{
451471
name: 'qux',
452472
params: { author: 'jane' },
473+
path: '/foo/bar',
453474
},
454475
],
455476
},
@@ -483,6 +504,7 @@ it('gets navigate action from state with config', () => {
483504
author: 'jane',
484505
},
485506
screen: 'qux',
507+
path: '/foo/bar',
486508
initial: true,
487509
},
488510
screen: 'bar',
@@ -499,6 +521,7 @@ it('gets navigate action from state for top-level screen with config', () => {
499521
{
500522
name: 'foo',
501523
params: { answer: 42 },
524+
path: '/foo/bar',
502525
},
503526
],
504527
};
@@ -519,6 +542,7 @@ it('gets navigate action from state for top-level screen with config', () => {
519542
payload: {
520543
name: 'foo',
521544
params: { answer: 42 },
545+
path: '/foo/bar',
522546
},
523547
type: 'NAVIGATE',
524548
});
@@ -539,7 +563,7 @@ it('gets navigate action from state with 2 screens including initial route and w
539563
name: 'qux',
540564
params: { author: 'jane' },
541565
},
542-
{ name: 'quz' },
566+
{ name: 'quz', path: '/foo/bar' },
543567
],
544568
},
545569
},
@@ -571,6 +595,7 @@ it('gets navigate action from state with 2 screens including initial route and w
571595
params: {
572596
screen: 'quz',
573597
initial: false,
598+
path: '/foo/bar',
574599
},
575600
},
576601
},
@@ -593,7 +618,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
593618
name: 'qux',
594619
params: { author: 'jane' },
595620
},
596-
{ name: 'quz' },
621+
{ name: 'quz', path: '/foo/bar' },
597622
],
598623
},
599624
},
@@ -631,7 +656,7 @@ it('gets navigate action from state with 2 screens without initial route and wit
631656
author: 'jane',
632657
},
633658
},
634-
{ name: 'quz' },
659+
{ name: 'quz', path: '/foo/bar' },
635660
],
636661
},
637662
},
@@ -856,6 +881,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
856881
{
857882
name: 'qux',
858883
params: { author: 'jane' },
884+
path: '/foo/bar',
859885
},
860886
{ name: 'quz' },
861887
],
@@ -889,6 +915,7 @@ it('gets navigate action from state with more than 2 screens with lower index',
889915
params: {
890916
screen: 'qux',
891917
initial: false,
918+
path: '/foo/bar',
892919
params: {
893920
author: 'jane',
894921
},

packages/core/src/__tests__/getPathFromState.test.tsx

+11-7
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ it('converts state to path string with config', () => {
8282
routes: [
8383
{
8484
name: 'Baz',
85-
params: { author: 'Jane', valid: true, id: 10 },
85+
params: {
86+
author: 'Jane',
87+
id: 10,
88+
valid: true,
89+
},
8690
},
8791
],
8892
},
@@ -186,9 +190,9 @@ it('handles state with config with nested screens', () => {
186190
{
187191
name: 'Baz',
188192
params: {
193+
answer: '42',
189194
author: 'Jane',
190195
count: '10',
191-
answer: '42',
192196
valid: true,
193197
},
194198
},
@@ -265,9 +269,9 @@ it('handles state with config with nested screens and exact', () => {
265269
{
266270
name: 'Baz',
267271
params: {
272+
answer: '42',
268273
author: 'Jane',
269274
count: '10',
270-
answer: '42',
271275
valid: true,
272276
},
273277
},
@@ -333,9 +337,9 @@ it('handles state with config with nested screens and unused configs', () => {
333337
{
334338
name: 'Baz',
335339
params: {
340+
answer: '42',
336341
author: 'Jane',
337342
count: 10,
338-
answer: '42',
339343
valid: true,
340344
},
341345
},
@@ -399,9 +403,9 @@ it('handles state with config with nested screens and unused configs with exact'
399403
{
400404
name: 'Baz',
401405
params: {
406+
answer: '42',
402407
author: 'Jane',
403408
count: 10,
404-
answer: '42',
405409
valid: true,
406410
},
407411
},
@@ -476,9 +480,9 @@ it('handles nested object with stringify in it', () => {
476480
{
477481
name: 'Bis',
478482
params: {
483+
answer: '42',
479484
author: 'Jane',
480485
count: 10,
481-
answer: '42',
482486
valid: true,
483487
},
484488
},
@@ -558,9 +562,9 @@ it('handles nested object with stringify in it with exact', () => {
558562
{
559563
name: 'Bis',
560564
params: {
565+
answer: '42',
561566
author: 'Jane',
562567
count: 10,
563-
answer: '42',
564568
valid: true,
565569
},
566570
},

0 commit comments

Comments
 (0)