-
Notifications
You must be signed in to change notification settings - Fork 147
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
Parallel Routes #426
base: main
Are you sure you want to change the base?
Parallel Routes #426
Conversation
🦋 Changeset detectedLatest commit: fffdf1c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Ok looking at this now. First couple notes on the PR. I've done some updates so this will need to be rebased. Should be minor but putting that out there. Also you might notice I tend to avoid optional chaining in code. Mostly it is because some CDNs have issues with it. I got into that habit with Solid core and have continued with the other libraries. That's why there are so many chained As for the feature itself. Yeah I think it is pretty compelling and doesn't look like it adds too much extra code . I do think we need to have opinions on the unresolved things. Can you show me the syntax for the default for a slot. Is it another field at the same level as component/children directly inside the slot.. like: breadcrumbs: {
component: (props) => <header>{props.children}</header>,
default: {
component: //...
}
children: //.... And you are saying that is the equivalent of adding a children with path More generally slots can have slots right?.. Basically anywhere there is a Match Persistence seems like it is one of the reasons people would go for such an approach in the first place. It is interesting that on refresh you need default slots because not all navigation information is encoded in the URL. But I have to admit I'm having a hard time picturing any UI that looked like that Next example that I've ever used that had any connection to routing. Honestly I'm having a hard time looking past breadcrumbs or maybe like more info pangels for the use of this feature. That's enough mind you. But I feel things like Match Persistence are the things this can do that would be hard otherwise. In any case I've seen a number of thumbs up on the issue and there is obviously interest on my part. I'd love to understand the use cases or how people would use the feature a bit more because while it looks like the code doesn't get much trickier this is a big concept to teach and explain. |
Ah ok, if you want I can configure rollup and typescript to transpile away the optional chains so we can keep the nicer dx
It'd be similar to what you did except the breadcrumbs: {
component: (props) => <header>{props.children}</header>,
default: (props) => "Default",
children: //....
I think so, but i'm not 100% certain on the behaviour of
Yes, as an example Mattrax uses a few nested slots. We have the
Basically yeah, since
This isn't the case, export type RouteDefinition = {
path?: string;
children?: RouteDefinition | RouteDefinition[];
slots?: Record<string, Omit<RouteDefinition, 'path'> & { default?: Component }>
}
The slots themselves wouldn't have path params, but the slot's children could. Note that
Yeah I'm not sure when I'd actually use match persistence, but having it seems nice since it's a feature that has to be implemented at router-level. |
After playing with the Next example more, I've realised this implementation doesn't act the same way as Next. |
turns out 'rename branch' just means 'delete and recreate with a new name' 🤦 |
Is there any help currently needed with this PR to get consideration continued? I'm increasingly finding that our designs have needs that could be much easier accomplished with this parallel routes support instead of needing to wrap every route file in the generic layout and passing props individually. The ability to have more generic layouts higher up and avoid re-rendering large parts of them, while still changing others would save a lot of hassle. Happy to beta test or help out if we think this can keep moving forward! |
I haven't had time to make this implementation more like Next's, and at some point I'd like to chat with @tannerlinsley about his ideas for parallel routes. Some help with the former would be appreciated, though the router's internals are quite something to navigate 😅 |
This PR adds basic support for Parallel Routes, a feature I've only seen in Next.js.
The following is my reasoning for why Solid Router should support them, but I think Next and this Remix Proposal do a much better job of explaining them.
Proposal
Parallel routes allow layouts to have multiple children, aka slots. Each of these slots define their own routes, generate their own set of matches, and can be placed anywhere in the layout just like regular children (children in fact are just a fancy slot).
This is useful for situations like the following from Mattrax (simplified for this example).
At a high level, there's a root layout that renders a header including breadcrumbs, and then the actual page is rendered below that.
Currently, the header would need to use something like
useCurrentMatches
, deciding which breadcrumbs should be rendered manually.With parallel routes, the breadcrumbs become their own slot. The router calculates a separate branch of matches specifically for the breadcrumbs' routes, and provides the rendered components inside
props.slots.breadcrumbs
instead ofprops.children
. It really is just allowing multiple sets of children.The config for this would look something like this:
Another thing that's great about this is that since slots get their own matches, they also get their own loaders, so our breadcrumbs can define the exact data they need to be preloaded at a route level, rather than having to cram it all into one loader for the root layout.
Unresolved Questions
Default
In Next.js, when a slot mounts but doesn't have any matches it will render a
default.js
view if one is available.I'm not sure if solid router needs an equivalent
default: ...
field for slots. Catchall routes in the slot + its nested children do the same job (as they would in nextjs), but having a singledefault
option to fall back on would be kinda nice.It would also need to be used in the case that only slots have matches and no children do, in which case the layout route itself would need
default
rather than its slots.To see this in action, go to this playground and notice that while the
audience
slot has a match, the other slots do not and are fallback back to theirdefault
page. It's basically a catchall that applies to nested routes too.Also notice that loading the home route and the navigating to a slot-specific route doesn't display the
default
page, because of the below behaviour.Match persistence
In Next.js, when slot A and B both have a match, but then a navigation happens that causes only slot A to have a match, slot B will continue display the match it had previously. I haven't implemented this but it seems pretty cool to have.
To see this in action, navigate between routes in the
audience
slot of this playground and notice that theviews
slot remains on 'Home'. If you navigate between the routes in theviews
slot, the last selected route in theaudience
slot remains selected, unless 'Home' is picked in either slot, at which point both slots match and 'Home' ends up being selected in both slots.TODO before merge
Initial navigations between breadcrumb layers in Mattrax currently cause the navItems slot to remountWas being caused by not accounting forprops.children
being undefined while lazy loading nav items