-
-
Notifications
You must be signed in to change notification settings - Fork 143
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
Support for parent
prop in Router
#265
Conversation
size-limit report 📦
|
Codecov ReportBase: 100.00% // Head: 100.00% // No change to project coverage 👍
Additional details and impacted files@@ Coverage Diff @@
## master #265 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 7 7
Lines 255 262 +7
Branches 50 50
=========================================
+ Hits 255 262 +7
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. |
index.js
Outdated
|
||
// nested `<Router />` has the scope of its closest parent router (base path is prepended) | ||
// Routers are not nested by default, but this might change in future versions | ||
const proto = nested ? parent : defaultRouter; |
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.
This change is ok... however... it will lead to inefficiencies down the road. Because you are going to want to change <Route>
at some point to support nested routes (you can calculate base
automatically from the path pattern!!!)... in which case, you will already have the parent router and can supply it directly to the child <Router>
without having to call useContext()
a second time redundantly. In addition, for non-nested routers, you are now adding an extra useContext()
call for no reason.
My recommendation would be to simply supply parent
instead of a nested
flag. This makes the routers more powerful and maximally efficient.
People want the nested router ability so that they can wrap your library with their own code for nested Routes. So, it is more much important to make Routers powerful than marginally more concise, since actual usage would be at the Route
level, not the Router
level. A more powerful Router is merely the first step in building more powerful Routes. Ideally, users shouldn't have to use Routers at all.
But once nested Routers are working, you would then be free to turn your focus to allowing users to not have to use Routers at all, by instead allowing a nested Route
to work and auto-calculate the base for the nested child router beneath them. ("nested" would be a good flag for Routes instead of Routers!)
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.
This change is ok... however... it will lead to inefficiencies down the road. Because you are going to want to change
<Route>
at some point to support nested routes (you can calculatebase
automatically from the path regexp!!!)... in which case, you will already have the parent router and can supply it directly to the nested<Router>
without having to calluseContext()
a second time redundantly. In addition, for non-nested routers, you are now adding an extra hook call for no reason.My recommendation would be to supply
parent
instead of anested
flag. This makes the routers more powerful. And then, turn your focus to allowing users to not have to use Routers at all, by instead allowing nestedRoute
to work and auto-calculate the base for the nested router beneath them. ("nested" would be a good flag for Routes instead of Routers!)
Not sure if I follow, could you please provide some example on how router and nested routes could be used together?
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.
@molefrog Sure, I will put some examples together from my own codebase. Will get back to you soon.
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.
@molefrog this is (roughly) how my own codebase defines Route
:
function splitPathIntoBaseAndRest(pattern: Path, path: Path, matchParams: DefaultParams) {
const matchKey = /:([^/:]+)\*$/.exec(pattern);
if (!matchKey) return null;
const rest = matchParams[matchKey[1]];
if (rest == null || !path.endsWith("/" + rest)) return null;
return [path.substring(0, path.length - rest.length - 1), "/" + rest];
}
export function Route<T extends DefaultParams | undefined = undefined, RoutePath extends Path = Path>(props: RouteProps<T, RoutePath>) {
const {path, children, component: Component} = props;
const parentRouter = useRouter();
const [pathToMatch] = parentRouter.hook(parentRouter); // note: this is not the default hook
const [matches, params] = (props as any).match as Match ?? parentRouter.matcher(path ?? '', pathToMatch);
if (!matches) {
return null;
}
const [base, rest] = splitPathIntoBaseAndRest(path ?? '', pathToMatch, params!) ?? [pathToMatch, ''];
// Example of above function:
// Input: pathToMatch "/posts/1234/comments/5678" split using pattern "posts/:postId/:_*"
// Output: base = "/posts/1234"; rest = "/comments/5678"
// THE IMPORTANT TAKEAWAY:
// I already needed to use the parent router to perform my calculation,
// so now I can just pass it in below as a prop, extra useContext() call is unnecessary!!
// ↓ ↓
return <Router parent={parentRouter} base={base} hook={myCustomHookThatReturnsRest}>
// actual children go here (omitting for brevity, but they are a function of `rest`)
</Router>;
}
As you can see... in performing the calculation to allow nested Routes, I already have access to the parent router so it seems silly to require an extra useContext
call when I can just pass it in as a prop.
To illustrate how this actually works in jsx:
<Route path='/posts/:postId'>
<Route path='comments/:commentId'> // matches "/posts/1234/comments/5678"
...
</Route
</Route>
If you want to do something like this as well in the future (which seems very likely since this is incredibly powerful), then you'll probably not want to add superfluous useContext
calls either. If you allow parent={useRouter()}
syntax, then you future-proof yourself in this respect. (While also avoiding unnecessary overhead for non-nested routers).
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.
Thanks, I'll review the code and get back to you tomorrow.
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.
Alright, I think I finally get it.
("nested" would be a good flag for Routes instead of Routers!)
That's the most important note here! For some reason I thought that maybe we could use nested routers as a replacement for nested routes, but now it makes more sense to have a semi-private prop (for advanced usage) that'll help us to build nested route functionality and much more.
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.
See 5963014
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.
Looks awesome! 🎉
Great! Thank you for your help @HansBrende. |
@molefrog sure thing, I can work on that. |
@HansBrende FYI I've decided to release it, the change has landed in 2.9.0. |
It's an alternative solution to #231.
Notable differences:
<Router />
is immutable (it only gets created once and doesn't react on props change) we can simply make a copy of the parent router, it does not have to be dynamic.parent={useRouter()}
which IMO could be not obvious for some users. Instead, by adding anested
prop, we the the router to inherit the base path. I even think that we can enable this by default in future releases.