-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[v6] feature suggestion: possibility to use nested routes with absolute paths #7335
Comments
A nested route with an absolute path that does not exactly match its parent routes' paths is not valid, so any child routes that you define in this way will have to match their parent paths exactly. While we could technically enforce this, it doesn't seem like it provides much benefit. Why not just use relative URLs? |
Well, I have two reasons for not using relative URLs.
|
I second everything @smashercosmo says. Most frontend-y apps (including ours) have been built with some
Rewriting all of this would be painful. We currently would like to do something like this: // Top level App.tsx
<Router>
<Routes>
<Route path="/:teamSlug/*" element={<Team />} />
</Routes>
</Router>
// "Layout" for all things in teamSlug
const Team = () => (
<div>
<Sidebar />
<Routes>
{/* These absolute paths won't work when this is nested inside <Team /> */}
<Route path="/:teamSlug/settings" element={<TeamSettings />} />
<Route path="/:teamSlug/:projectSlug" element={<Project />} />
</Routes>
</div>
) I'm kinda new to React Router: there might be alternate ways of achieveing this. One of the main reasons we've got absolute routes is because of named routes with typed parameters. With Typescript, we can have a type of all routes, as well as their parameters: // routes.ts
interface Team {
to: 'team',
params: { teamSlug: string },
}
interface Project {
to: 'project',
params: { teamSlug: string, projectSlug: string },
}
export type AppRoute = Team | Project;
// Used as: <Route path={routes.team} /> in app code
export const routes: { [name: string]: string } = {
team: '/:teamSlug',
project: '/:teamSlug/:projectSlug',
} import { routes, AppRoute } from './routes';
import {
Link as RouteLink,
LinkProps as RouteLinkProps,
} from 'react-router-dom';
// Link.tsx
type LinkProps = AppRoute & Omit<RouteLinkProps, 'to'>;
export const Link: React.FunctionComponent<LinkProps> = ({ to, params, ...rest}) => {
let path = routes[to]; // path=/:teamSlug
if (params) {
for (const [key, val] of Object.entries(params)) {
path = path.replace(`:${key}`, (_) => String(val));
}
}
return <RouteLink ref={ref} to={path} {...rest} />;
}; // app code
import { Link } from './Link';
<Link to="team" params={{ teamSlug: "foo" }}>Team</Link>
<Link to="project" params={{ teamSlug: "foo", projectSlug: "bar" }}>Project</Link>
{/* Won't compile */}
<Link to="project" params={{ teamSlug: "foo" }}>Missing param</Link>
<Link to="foo" params={{ teamSlug: "foo", projectSlug: "bar" }}>Incorrect `to`</Link> |
I hear you about rewriting all your existing route paths. I'd like to avoid that if we can. One case that I think will be very confusing for people is with <Route path="settings" element={SettingsPage}>
<Route path="/" element={<SettingsIndex />} />
</Route> In this case, it's pretty clear that Perhaps, since you have all absolute paths, you could dynamically generate your routes by looping over your array/object in a |
Perhaps this could be solved by an |
This doesn't look natural to me at all. Path, that starts with forward slash, always indicates that this is a root path. IMHO using path-less routes for index routes feels much more natural. <Route path="settings" element={SettingsPage}>
<Route element={<SettingsIndex />} />
</Route>
Good idea, but I think it should be on <Routes mode="absolute">
<Route path="/some-url" element={<SomeElement />}>
<Route path="/some-url/some-other-url" element={<SomeOtherElement />}>
</Route>
</Routes> |
What does your current route config look like in v5 @smashercosmo? Are you doing the whole thing in JSX with absolute paths? |
In case this is helpful for anyone, what I'm doing is just wrapping my absolute paths in a function which converts them into the relative form expected by
Keeps the readability benefit and means if you have all your absolute paths defined in one place already, you don't have to refactor that and can just use it as-is. From @smashercosmo example, it'd look like this
Or if you had paths defined in a
Super simple and, for me, does the job. I guess it might be nice to support absolute mode as a native feature but that adds API weight and complexity, and I guess this pattern might be an acceptable alternative that already works? |
I think the big problem is, that in RRv6 |
@MeiKatz just to clarify was that in response to my post? :-) |
@davnicwil Kind of. I just wanted to point out, why this change could make some confusions if done wrong. |
@MeiKatz I agree. At least how I'm thinking about it is that the trailing I therefore don't try to configure my 'root' components with actual paths from my What's confusing is that, unlike I agree that a good way to solve this might be to make this 'root' special char something different, something more obviously a special char, like Weirdly though, to sidestep this completely, playing around I've noticed that in fact both leaving |
Are absolute paths in We have rather a lot of routes, and we define them a bit like this:
The path and the function that builds the path are next to each other because they're two sides of the same coin: one is used for a It seems weird and brittle to then have to define a separate relative path for a |
@mjackson We've now spent some time trying to making this work in our app, and having to use relative paths in It's very helpful to be able to say, from anywhere, As it is, without absolute paths as an option, The only solutions I can think of are: (a) define all the path constants twice, one absolute and one relative (feels brittle); or Am I missing something? Being able to say |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
@timdorr do you mind adding fresh label to the issue? |
There you go. |
With the latest TypeScript features (https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#template-literal-types), using absolute urls across application becoming even more relevant. Now we can do something like this: paths.ts export const USER_POST_URL = '/users/:userId/posts/:postId' UserPost.tsx import { useParams } from 'react-router-dom'
import * as PATHS from '../paths.ts'
import type { Params } from '../types.ts'
function UserPost() {
const { userId, postId } = useParams() as Params<PATHS.USER_POST_URL>
} |
I feel compelled to jump in here also. To be consistent with HTML standards and dev expectations, IMO
Instead of using the current
While using |
@ThomasBurleson Exactly. That's exactly the way that I would implement it. The currently use of |
I would like to have support for absolute paths, because routes for me look something like this: export const pathTo = '/path/to';
export const route1 = `${pathTo}/my/route1`;
export const route2 = `${pathTo}/my/route2`; With the above, I could e.g. use If I were forced to use relative paths for route configuration, then I would have to do something like this to try to be as DRY as possible: // root path
export const pathTo = '/path/to';
// subpath definitions
export const route1SubPath = `/my/route1`;
export const route2SubPath = `/my/route2`;
// full path definitions
export const route1FullPath = `${pathTo}${route1SubPath}`;
export const route2FullPath = `${pathTo}${route2SubPath}`; And using the previous example with Route and Link, I would need to do (Though it would make things better if react-router provided a tool to take a nested route configuration and spit back an amended configuration that would contain both the absolute and relative path for each route, because then in my IDE I could just find usages of each route object that would cover both the relative and absolute paths.) |
Any updates on this? Beta version is already out and it's a very important feature that's still missing. I'm implementing i18n, my URLs are translated and I have to work with absolute addresses, because the translation library doesn't have access to react router context. Adding workarounds for this makes everything super messy. |
agreed, relative paths are making navigation-routes definition too messy, because the path definition need to be composed by parent routes path to expose a "navigately" route 😕 |
It would be great to support absolute routes based on @ThomasBurleson's suggestion. I recently upgraded a large codebase to v6 beta (partially because we needed the Here are a couple of our use cases where having absolute routes is useful, or at least enables versatility:
|
Thanks everyone for chiming in here. I appreciate all the conversation. I definitely hear what you're saying re: following existing URL conventions @ThomasBurleson. i.e. starting with a / indicates an absolute URL and not starting with a / indicates a relative one. That's exactly what we do with My main hangup with including support for absolute URLs in nested That's why we built this feature the way we did, because it saves you from having to type out all your route paths from the root every time. For example, assuming we did support absolute paths in nested routes: <Routes>
<Route path="users" element={...}>
{/* THIS IS AN ERROR, since the parent route path will never match
a URL that begins with /customers */}
<Route path="/customers/:id" element={...} />
{/* Since the above route MUST start with /users or be
an error, it seems easier to just leave off the URL prefix */}
<Route path=":id" element={...} />
{/* Or if you really want it you could use an absolute path.
Just be sure it starts with all of its parent route paths! */}
<Route path="/users/:id" element={...} />
</Route>
</Routes> I get that most people who want this feature are probably using constants for their route paths instead of writing them out by hand, but the point still stands. A nested route with an absolute path must duplicate all path segments of its parent route or it will be unreachable. However, in the current architecture where all routes are relative to their parents it is not possible to create a nested route that is not reachable. I'm not saying that we absolutely cannot support this feature, but I am wondering what is the actual use case here? Because if we're going to introduce a feature that opens up the potential for errors, we better have a good reason. @andykant Would you be willing to expand on your use cases a little? I'm not quite sure what either of those mean. Some example code would be great. Thanks, everyone for your patience! |
@mjackson I think we have two problems in here:
The 1st point should be easy and should be done right now. The 2nd point could be implemented for those who use the feature unless there is a better solution that enable the use of absolute paths constants. |
@mjackson You are right about it being logical for Routes to ideally be relative paths. But my biggest issue is the lack of possibility to use some sort of shared reference between Routes and Links so that in your IDE you can easily find which Links point to which Routes and vice versa (since in React Router 6 you'd be forced to use separate string literals/constants between Links and Routes). In React Router 5, although not logical for the reasons you said, I was at least able to share absolute path constants between Routes and Links; now not anymore with RR 6. It would be great to have some tool like the following either built into or endorsed by React Router 6: That way the problem regarding shared references would be solved. |
@MeiKatz
With respect, this is naive. This change would most likely break ALL apps using React Router v6 since any nested So although this change might be easy to make in our code, it would be a difficult one for the community. The only reason I think it might still be possible to make is because we are still in beta on v6. But I know for a fact that many people are already using v6 in production, despite it not being ready yet.
I'm guessing this is how you would do this in v6 if we supported nested absolute paths, @amcsi: let USERS_PATH = '/users';
let USER_PATH = '/users/:id';
let USER_CREATE_PATH = '/users/new';
// routes
<Routes>
<Route path={USERS_PATH} element={<Users />}>
<Route path={USER_PATH} element={<UserProfile />} />
<Route path={USER_CREATE_PATH} element={<UserCreate />} />
</Route>
</Routes>
// links
<Link to={generatePath(USER_PATH, { id: 123 })}>User 123</Link> Is that close? |
You said it: RRv6 is in still in beta and the reason why some people use it in production is, that the development of this next major version stopped for a long time. People migrating from v5 to v6 have to change there code anyway. What I meant with consistent behaviour: relative paths in |
@mjackson yes that is pretty much how I would do it if you supported nested absolute paths. With that I can find usages of the USER_PATH constant in my IDE and find the route and all the. I may in addition assemble USER_PATH from USERS_PATH to be DRY and lessen the chances of writing invalid nested absolute routes: const USER_PATH = `${USERS_PATH}/:id`; |
@mjackson Right now, because we define all path strings in a single file, we're having to do stuff like this: let USERS_PATH = '/users';
let USER_PATH = '/users/:id';
let generateUserPath = id => ({ id }) => `/users/${id}`;
let RELATIVE_USER_PATH = ':id';
let USER_CREATE_PATH = '/users/new';
let RELATIVE_USER_CREATE_PATH = 'new';
// routes
<Routes>
<Route path={USERS_PATH} element={<Users />}>
<Route path={RELATIVE_USER_PATH} element={<UserProfile />} />
<Route path={RELATIVE_USER_CREATE_PATH} element={<UserCreate />} />
</Route>
</Routes>
// links
<Link to={USER_CREATE_PATH}>Create a user</Link>
<Link to={generateUserPath({ id: 123 })}>User 123</Link> This is not very nice, and perhaps isn't the most elegant way to solve the problem. But as we want to define all our paths in the same place, while also having an app-level The awkward If you're defining all your path constants in the same place - which I think is a fairly common practice - having to do fiddly workarounds actually increases the risk of unreachable routes. It would be simpler and more robust if |
Thanks everyone for the discussion here, and for helping us to better understand your use cases. We've decided to go ahead and add support for nested absolute paths in v6. Hopefully this makes it easier for you to define your route paths in a separate file and track where they are being used in your app. Please be aware that if you are currently using the v6 beta any absolute paths in nested routes will Will follow up in #7992. |
- Nested routes with absolute paths that do not match their parent path are now an error - Also adds <Route index> prop for index routes Fixes #7335
- Nested routes with absolute paths that do not match their parent path are now an error - Also adds <Route index> prop for index routes Fixes #7335
Awesome, thank you!
Sure! To be clear, the main point that I was raising is that these are patterns that were available in v5 and require non-trivial refactors/testing in order to upgrade to v6. These can still be done in v6, but the v6 version has pros (more explicit) and cons (more boilerplate). Displaying conditional route-specific content in a reusable/shared component.
Applying additional route variants to a more generic parent route. v5 pseudocode:
|
Also: - Add (optional) `<Route index>` prop for index routes - Return original route objects from `matchRoutes` - Remove `PartialRouteObject` interface, use `RouteObject` instead - Remove `createRoutesFromArray` Fixes #7335
Also: - Add (optional) `<Route index>` prop for index routes - Return original route objects from `matchRoutes` - Remove `PartialRouteObject` interface, use `RouteObject` instead - Remove `createRoutesFromArray` Fixes #7335
This was fixed in #7992. It will be released in v6 beta 4 later today. |
I want to be able to do the following
routes.js
SomeElement.js
SomeOtherElement.js
And if user lands on /some-url/some-other-url, he should see
Motivation:
All paths in our app are defined as absolute paths like this
paths.js
The text was updated successfully, but these errors were encountered: