Skip to content

Commit cd47915

Browse files
authored
fix: handle no path property and undefined query params (react-navigation#7911)
1 parent d649fbc commit cd47915

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-0
lines changed

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

+206
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,209 @@ it('returns "/" for empty path', () => {
521521

522522
expect(getPathFromState(state, config)).toBe('/');
523523
});
524+
525+
it('parses no path specified', () => {
526+
const path = '/Foo/bar';
527+
const config = {
528+
Foo: {
529+
screens: {
530+
Foe: {},
531+
},
532+
},
533+
Bar: 'bar',
534+
};
535+
536+
const state = {
537+
routes: [
538+
{
539+
name: 'Foo',
540+
state: {
541+
routes: [{ name: 'Bar' }],
542+
},
543+
},
544+
],
545+
};
546+
547+
expect(getPathFromState(state, config)).toBe(path);
548+
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
549+
});
550+
551+
it('parses no path specified in nested config', () => {
552+
const path = '/Foo/Foe/bar';
553+
const config = {
554+
Foo: {
555+
path: 'foo',
556+
screens: {
557+
Foe: {},
558+
},
559+
},
560+
Bar: 'bar',
561+
};
562+
563+
const state = {
564+
routes: [
565+
{
566+
name: 'Foo',
567+
state: {
568+
routes: [
569+
{
570+
name: 'Foe',
571+
state: {
572+
routes: [{ name: 'Bar' }],
573+
},
574+
},
575+
],
576+
},
577+
},
578+
],
579+
};
580+
581+
expect(getPathFromState(state, config)).toBe(path);
582+
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
583+
});
584+
585+
it('strips undefined query params', () => {
586+
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
587+
const config = {
588+
Foo: {
589+
path: 'foo',
590+
screens: {
591+
Foe: {
592+
path: 'foe',
593+
},
594+
},
595+
},
596+
Bar: 'bar/:type/:fruit',
597+
Baz: {
598+
path: 'baz',
599+
screens: {
600+
Bos: 'bos',
601+
Bis: {
602+
path: 'bis/:author',
603+
stringify: {
604+
author: (author: string) =>
605+
author.replace(/^\w/, (c) => c.toLowerCase()),
606+
},
607+
parse: {
608+
author: (author: string) =>
609+
author.replace(/^\w/, (c) => c.toUpperCase()),
610+
count: Number,
611+
valid: Boolean,
612+
},
613+
},
614+
},
615+
},
616+
};
617+
618+
const state = {
619+
routes: [
620+
{
621+
name: 'Bar',
622+
params: { fruit: 'apple', type: 'sweet' },
623+
state: {
624+
routes: [
625+
{
626+
name: 'Foo',
627+
state: {
628+
routes: [
629+
{
630+
name: 'Baz',
631+
state: {
632+
routes: [
633+
{
634+
name: 'Bis',
635+
params: {
636+
author: 'Jane',
637+
count: 10,
638+
answer: undefined,
639+
valid: true,
640+
},
641+
},
642+
],
643+
},
644+
},
645+
],
646+
},
647+
},
648+
],
649+
},
650+
},
651+
],
652+
};
653+
654+
expect(getPathFromState(state, config)).toBe(path);
655+
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
656+
});
657+
658+
it('handles stripping all query params', () => {
659+
const path = '/bar/sweet/apple/foo/bis/jane';
660+
const config = {
661+
Foo: {
662+
path: 'foo',
663+
screens: {
664+
Foe: {
665+
path: 'foe',
666+
},
667+
},
668+
},
669+
Bar: 'bar/:type/:fruit',
670+
Baz: {
671+
path: 'baz',
672+
screens: {
673+
Bos: 'bos',
674+
Bis: {
675+
path: 'bis/:author',
676+
stringify: {
677+
author: (author: string) =>
678+
author.replace(/^\w/, (c) => c.toLowerCase()),
679+
},
680+
parse: {
681+
author: (author: string) =>
682+
author.replace(/^\w/, (c) => c.toUpperCase()),
683+
count: Number,
684+
valid: Boolean,
685+
},
686+
},
687+
},
688+
},
689+
};
690+
691+
const state = {
692+
routes: [
693+
{
694+
name: 'Bar',
695+
params: { fruit: 'apple', type: 'sweet' },
696+
state: {
697+
routes: [
698+
{
699+
name: 'Foo',
700+
state: {
701+
routes: [
702+
{
703+
name: 'Baz',
704+
state: {
705+
routes: [
706+
{
707+
name: 'Bis',
708+
params: {
709+
author: 'Jane',
710+
count: undefined,
711+
answer: undefined,
712+
valid: undefined,
713+
},
714+
},
715+
],
716+
},
717+
},
718+
],
719+
},
720+
},
721+
],
722+
},
723+
},
724+
],
725+
};
726+
727+
expect(getPathFromState(state, config)).toBe(path);
728+
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
729+
});

packages/core/src/getPathFromState.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export default function getPathFromState(
6464
};
6565
let currentOptions = options;
6666
let pattern = route.name;
67+
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
68+
let nestedRouteNames = '';
6769

6870
while (route.name in currentOptions) {
6971
if (typeof currentOptions[route.name] === 'string') {
@@ -77,11 +79,13 @@ export default function getPathFromState(
7779
}).screens
7880
) {
7981
pattern = (currentOptions[route.name] as { path: string }).path;
82+
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
8083
break;
8184
} else {
8285
// if it is the end of state, we return pattern
8386
if (route.state === undefined) {
8487
pattern = (currentOptions[route.name] as { path: string }).path;
88+
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
8589
break;
8690
} else {
8791
index =
@@ -92,18 +96,25 @@ export default function getPathFromState(
9296
}).screens;
9397
// if there is config for next route name, we go deeper
9498
if (nextRoute.name in deeperConfig) {
99+
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
95100
route = nextRoute as Route<string> & { state?: State };
96101
currentOptions = deeperConfig;
97102
} else {
98103
// if not, there is no sense in going deeper in config
99104
pattern = (currentOptions[route.name] as { path: string }).path;
105+
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
100106
break;
101107
}
102108
}
103109
}
104110
}
105111
}
106112

113+
if (pattern === undefined) {
114+
// cut the first `/`
115+
pattern = nestedRouteNames.substring(1);
116+
}
117+
107118
// we don't add empty path strings to path
108119
if (pattern !== '') {
109120
const config =
@@ -147,6 +158,12 @@ export default function getPathFromState(
147158
if (route.state) {
148159
path += '/';
149160
} else if (params) {
161+
for (let param in params) {
162+
if (params[param] === 'undefined') {
163+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
164+
delete params[param];
165+
}
166+
}
150167
const query = queryString.stringify(params);
151168

152169
if (query) {

0 commit comments

Comments
 (0)