Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: warn on duplicate screen names across navigators
- Loading branch information
Showing
4 changed files
with
77 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { NavigationState, PartialState } from '@react-navigation/routers'; | ||
|
||
export default function checkDuplicateRouteNames(state: NavigationState) { | ||
const allRouteNames = new Map<string, string[]>(); | ||
|
||
const getRouteNames = ( | ||
location: string, | ||
state: NavigationState | PartialState<NavigationState> | ||
) => { | ||
state.routeNames?.forEach((name) => { | ||
const current = allRouteNames.get(name); | ||
const currentLocation = location ? `${location} > ${name}` : name; | ||
|
||
if (current) { | ||
current.push(currentLocation); | ||
} else { | ||
allRouteNames.set(name, [currentLocation]); | ||
} | ||
}); | ||
|
||
state.routes.forEach((route: typeof state.routes[0]) => { | ||
if (route.state) { | ||
getRouteNames( | ||
location ? `${location} > ${route.name}` : route.name, | ||
route.state | ||
); | ||
} | ||
}); | ||
}; | ||
|
||
getRouteNames('', state); | ||
|
||
const duplicates = Array.from(allRouteNames.entries()).filter( | ||
([_, value]) => value.length > 1 | ||
); | ||
|
||
return duplicates; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Object.fromEntries is not available in older iOS versions | ||
export default function fromEntries<K extends string, V>( | ||
entries: (readonly [K, V])[] | ||
) { | ||
return entries.reduce((acc, [k, v]) => { | ||
if (acc.hasOwnProperty(k)) { | ||
throw new Error(`A value for key '${k}' already exists in the object.`); | ||
} | ||
|
||
acc[k] = v; | ||
return acc; | ||
}, {} as Record<K, V>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
02a031e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @satya164
I've following your work in the v6 roadmap and came across this change. I'd like to share an issue I see with this change.
Having repeated screen names is something that I found quite helpful when sharing screen components in different stacks:
Let's say I share ScreenA and ScreenB in different stacks (Tab1 and Tab2), in ScreenA I could do
navigation.navigate('ScreenB')
regardless of the stack that is being used.However, if repeating names is considered a bad practice what would be the recommended way to handle those cases?
Something that comes to mind is adding a param that somehow represent which stack is being used and then:
I've been working on type-safe navigation at Shop in Shopify, and already find it challenging to get type-safety in shared screens using v5. It would be amazing to come up with an official guide around sharing screens and I would be happy to contribute to it.
Sorry if this is not the best channel to ask these kind of questions, let me know if you think it's more suitable to create an issue in the repo or other alternatives for better communication
02a031e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbalay why not have those screens in a parent stack instead of in 2 stacks under tab1 and tab2?
02a031e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation is that I still want the tabbar to be visible when navigating to those screens.
Is there a better way of achieving it having the screens in the parent stack of the tabs navigator?
02a031e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes sense, though doesn't it mean there will be multiple instances of same screen which would lead to higher memory usage?
the original motivation for this warning was to discourage the case where people often nest a screen inside another with the same name. maybe the warning can be more relaxed to only warn for direct descendents instead of across the tree.
02a031e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be true in some cases, but I think there is still a use case for it. I'll expand a little bit on our specific case:
Shop has a home tab focused on order tracking, that display a list of shopping orders. When users tap on an item of that list we navigate to a screen with details of the order (Screen A).
We also have another tab focused on shopping, and among other things we show what shops the user is following. When tapping on a shop, we redirect to a screen that represents the shop and shows shopping orders made to that shop. Tapping on an order goes to order details (Screen A).
Both tabs have very different goals, so we want to have the tabbar visible to let the user go back and forth between different features.
It could happen that at some point we open the same order detail (Screen A) in both tabs, but from the app perspective it's not entirely wrong.
We also have a case for that, I can expand as well if you think these examples are helpful.