-
-
Notifications
You must be signed in to change notification settings - Fork 10.5k
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
RFC: Relative Links and Routes #5127
Comments
I think the expected behavior is better because expected with that syntax. The route walking behavior could be useful but I'd suggest looking for another syntax. |
Ah so maybe Not excited about inventing syntax though. |
What about a separate prop that is mutually exclusive with |
I think going up a matched route would be a bad idea. 2 reasons I can think of off the top of my head:
|
I think your assumption in first post is wrong, @ryanflorence Given URL like
|
@BTMPL Resolving here is going to be slightly different than how the browser treats hrefs. Before stripping the last segment, we ensure that the The only tricky thing with that is that we have to keep track of if the original path had a trailing slash. I dealt with that in #4459 with this code and these tests (which I think are correct, but I haven't reviewed them in a couple months). |
@pshrmn I understand that this is a decision aimed at new developers that might be unfamiliar with the URI syntax, but that's a step away from the specs. Also, Ryan's example was based on anchor tags, so I assumed spec compliant, not react-router variant and should follow the RFS - https://tools.ietf.org/html/rfc1808#section-5 Is there a real need for relative links other than not having to access |
The composability of React and RR4 are great. I think when @ryanflorence mentions the second option he is targeting 1st class support for this concept. Allowing a component to ignore implementation details of its owner/parent, etc. Conceptually, I think it's useful to classify path segments in 2 seperate ways. Navigation and parameter. An example I'm not sure what the exact solution is, but my most common needs are as follows:
I believe this description keeps cohesion within the component. The unfortunate thing about this discussion is that you might have a completely different view if you use RR4 differently within your app. But hoping this makes sense to as most people as possible. |
In my head Could the alternative behaviour be implemented in a dedicated component? |
I agree @thomasvm, except for needing a separate component. Especially with I don't believe I think @davidascher suggestion is worth considering. |
Relative links would be completely for convenience (i.e. not having to use I think that as long as the resolving behavior is well documented, I'm okay with being slightly different from how anchors resolve. The current URI spec seems mostly inspired by static file servers, where trailing slashes are normal (e.g. <!--
for example, with window.location.pathname = '/user/123',
you have to include the user id in a relative link
-->
<a href='123/edit'>Edit</a> If we automatically insert a trailing slash, then we get the behavior that I feel like the resolving was originally built around. Maybe I'm just assuming that intention because it fits my argument, but RFC 1808 was written over 20 years ago. Also, while this behavior is different from how anchors resolve, it is consistent with how the terminal apears to resolve with ~/code $ cd forks
~/code/forks $ ls
react-router history
~/code/forks $ cd react-router
~/code/forks/react-router $ cd ../history
~/code/forks/history $ Anyways, I'm more of a fan of the segment-wise double dot resolving because it is more predictable. I can certainly see the appeal to match based resolving, but I feel like that might lead to weird edge cases. |
I would be a fan of this solution if react-router would enforce traling slashes but I don't think enforcing anything like that is what the devs want. Maybe adding a separate link component or a prop on the link component to change it to relative mode is the solution?
Sure, but it's still in use today, and a lot of webdevs are used to how it works ;) |
@ryanflorence Why not follow standards? |
I came to this bug researching how to write relative Maybe links could just not support By disallowing <Route path='a/:aParam1/:aParam2' render={routeProps => <SubComponentA aParam1={routeProps.match.params.aParam1} aParam2={routeProps.match.params.aParam2}/>}/>
<Route path='b' render={routeProps => <SubComponentB buildALink={(aParam1, aParam2) => routeProps.match.url + '/' + ['a', aParam1, aParam2].map(encodeURIComponent).join('/')}/>}> I think this sort of pattern enables you to write components that can use Sorry if this isn’t coherent or isn’t quite on topic. My demo also probably uses react-router incorrectly. P.S., I also find it confusing that baseUrl=
However, route-relative URI support for |
I realized I made a mistake in my hypothetical relative <Route path='a/:aParam1/:aParam2' render={routeProps => <SubComponentA aParam1={routeProps.match.params.aParam1} aParam2={routeProps.match.params.aParam2}/>}/>
<Route path='b' render={routeProps => <SubComponentB buildALink={(aParam1, aParam2) => this.props.match.url + '/' + ['a', aParam1, aParam2].map(encodeURIComponent).join('/')}/>}> |
@binki I struggled a little to understand your example, but I agree with your point that we don't want sub-components making assumptions about their parent. As for removing a relative '..' as an option, that would be a little draconian for me. @ryanflorence Have you had a chance to read the above comments? Has your thinking progressed since opening this issue? |
The fact that /* current */
// location = { pathname: '/user/12345', ... }
// match = { path: '/user/:id', url: '/user/12345', params: { id: '12345' }, ... }
<Link to={`${match.params.id}/details`}>User Details</Link>
// <a href='/users/12345/details'>User Details</a> With that approach, you still have to use the The two solutions that come to mind are to make a separate Instead I think that we could add a // location = { pathname: '/user/12345', ... }
// match = { path: '/user/:id', url: '/user/12345', params: { id: '12345' }, ... }
// when adding additional segments, it is simpler to just write
// the desired segment to add
<Link spec to={`${match.params.id}/details`}>User Details</Link>
<Link to='details'>User Details</Link>
// both produce <a href='/users/12345/details'>User Details</a>
// when going to different segment on the same level, it is slightly simpler to
// just write the segment value
<Link spec to='67890'>Other User</Link>
<Link to='../67890'>Other User</Link>
// both produce <a href='/users/67890'>Other User</a> |
AFAIC we've already got a great story around relative // Relative links build their to value using match.url
<Link to={match.url + '/sub/path'}>relative link</Link>
// Relative routes build their path using match.path (or match.url if they don't care about parent route's params)
<Route path={match.path + '/sub/path'} .../> I agree these aren't perfect yet because the string concat can get messy when there's a trailing |
Avoiding match.url in case of <Route path={match.url+'/sub/path'} will be great. Instead of the above, if we were able to just say:
...it will be easier (where 'matchEnd' is a keyword that the 'Route' component can read and process). I ran into this 'path does not match unless prefixed with match.url' issue and resolved it through a careful look at https://reacttraining.com/react-router/web/example/basic I then went about removing all explicit pass-downs of props via:
(Before going through the example in detail, I had earlier tried to do prop pass-downs to check whether they somehow resolved the issue of the Route component not working). ....but now after the cleanup of these explicit/brute-force prop pass-downs (they were generating errors in the browser console about unwanted props being passed to various components), the component where I render my Route, now complains that it cannot find 'match'!!! |
Back in May (sorry, I'm late to this party), @lewisdiamond suggested that
I think he's right. This would mirror how regular web links work. As he also suggested, I also see and desire the benefit of going up a whole matched route, as @ryanflorence proposed. But rather than using In summary, my proposal would be:
If we leave trailing slashes for |
@svicalifornia not too sure whether '.' being used to remove part of the url matched so far, would really be an intuitive syntax in general...
...we should be able to say:
...? |
@sohrabsaran Not quite. As @ryanflorence proposed at the top of this thread:
So you wouldn't need any prefix for that. Just simply use My suggestion of
(See his post at the top of this thread for more context.) My proposal (echoing @lewisdiamond) is that
And to support the use case offered by @ryanflorence to go up a whole matched path, I propose that we use
For example, imaging we have a route
Now imagine that
Then inside the render function of
Or we could link to a different user's profile:
This is a somewhat contrived example, and you might structure the routes differently, but hopefully this shows how |
@sohrabsaran If you still doubt that
Open that file locally in Chrome and click the link, and you'll see that it removes the filename from the URL and takes you up to the directory path. It works the same way on a web server, and the web has worked that way since the 1990s. Following the existing conventions for relative paths in URLs will minimize developer confusion and increase adoption of this feature (as well as React and React Router). It's the right way to go. And with |
@svicalifornia thanks for summarising the context of the thread. I came here while looking for a way to avoid having to mention match.url in '<Route path='. I saw the last answer from @mjackson, that may have confused me about the exact scope of the opening post. I'll be more careful next time. So far I've avoided the need for '<Link to=' by using a custom component that calls history.push(). Agreed that 'no prefix' quite logically and concisely means that the given path is relative to match.url (the part of the url matched so far). Further, as suggested in the opening post, a Link's 'to' attribute when a relative path, is relative in the exact same way as a Route's 'path' attribute when a relative path. They are both considered as prefixed with match.url. So, I feel that it is logical and intuitive to consider a starting '..' as removing the end from match.url. I can't seem to visualise any alternate interpretation!
...resolves to:
...that in turn resolves to:
This is like the expected behavior of the 'cd' command on linux; given that the current directory contains two directories 'a' and 'b', if we say
...we see that the current directory changes to 'b'. This may be tried out at a linux command prompt. So @svicalifornia, can the following concern in the original post please be elaborated with an example (I take it that unlike me, you've understood the concern):
...? Do note that in the examples you've given, examples were given for '...' but not for '..'. Sorry for the bother, just trying to help to close out this one... |
@sohrabsaran In traditional web links, the behavior of
When the current URL path ends in slash, This behavior of relative links is defined in Internet standard RFC 1808, published in 1995. Netscape Navigator and Internet Explorer implemented relative web links this way back in the '90s, and web browsers have handled relative links exactly the same way since then. For traditional web sites, HTTP web servers usually take care standardizing directory URLs by redirecting any request to a directory URL without the trailing slash to the same URL but with the trailing slash added. This has the effect of adding a trailing slash to the web browser's address bar, so that all the relative links on the page will behave according to the URL ending in a trailing slash. This makes it easier for web developers to know how their relative links will work, because directory URLs will always end in a slash. It would be great if React Router could do something similar. While our routes don't correspond to directories on disk, they often do correspond to collections of items in a similar semantic sense. For example, we would expect a route with path React Router could standardize our collection/directory URLs for us in much the same way that HTTP servers do, via a new For relative links to be successfully adopted in React Router, they should behave exactly as relative links do on the traditional web, as defined in RFC 1808. To simplify this and avoid having to think about whether match URLs have trailing slashes, we should add a new Following these conventions will ensure that we are implementing relative links the same way that they have worked for over 20 years. Changing the expected behavior, even slightly, will introduce confusion and stumbling blocks for current and new developers alike, and I see no reason to do things differently when the existing conventions are known by millions of people and fairly easy to implement in React Router. The use case offered by @ryanflorence to go up a whole matched path (which may be one or more URL parts, absolute or relative to its own parent route) has no analogue in the traditional web. Therefore I think React Router should introduce a new path prefix |
Keep existing URL semantics is my vote. |
No description provided. |
No description provided. |
Btw, I sent a PR for basic implementation based on guys' earlier work here: #6307 |
I'm cross posting this from It doesn't implement relative links directly, but allows a nested browser router so that a basename can be set on a descendent of a BrowserRouter. Having a nested BrowserRouter (or equivalent) may be a common requirement in large applications that are separated in to modules. In our case, this is solely so that:
I looked in to the issues described by @timdorr above and think I have a solution. I created a https://github.com/penx/nested-browser-router I have put a demo usage here: https://codesandbox.io/s/q4l9vo1m89 <BrowserRouter>
<React.Fragment>
<h1>Application</h1>
<Link to="/">Home</Link> | <Link to="/sub">Sub app</Link> |{" "}
<Link to="/sub/sub">Sub sub</Link>
<Switch>
<Route exact path="/" render={() => <h2>Home</h2>} />
<Route
path="/sub"
render={() => (
<NestedBrowserRouter basename="sub">
<>
<h1>Sub app</h1>
<Link to="/">Sub Home</Link> | <Link to="/sub">Sub Sub</Link>
<Switch>
<Route exact path="/" render={() => <h2>Sub Home</h2>} />
<Route exact path="/sub" render={() => <h2>Sub Sub</h2>} />
</Switch>
</>
</NestedBrowserRouter>
)}
/>
</Switch>
</React.Fragment>
</BrowserRouter> Caveats:
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Bot DansGame |
@ryanflorence I've used the For example:
If I use the HOC Thanks! |
@JeroenReumkens @mjackson Maybe we should add the feature that |
Or maybe it could be an option to render a Also not sure about performance impact by rendering all these providers. |
@JeroenReumkens I think this is the "unsanctioned" method to get what you are looking for... import { Switch, Route, __RouterContext } from 'react-router';
...
const { match } = useContext(__RouterContext); I believe that will allow you to use the same approach. |
Just a quick update here for anyone who's following along: right now the plan is to make relative routing the default behavior in v6, similar to how it currently works in @reach/router today. Manually interpolating We will be introducing a version of |
Hi @mjackson Thanks for the updates! Will the Embedded Routers from |
@mjackson Where can we track the progress on that new relative version of |
@mjackson, is this functioanlity now available in Happy 2020 everyone. |
Relative routes and links are the default in v6. I'm not very optimistic that we can introduce relative links in v5 because some people are probably already relying on their I wrote up a section about relative links and routes in the v5 to v6 migration guide if you want to read more about how it works. But we can't change this in v5 w/out breaking some apps. |
It would be great if there were not only the one style for nested routes, but also the style with the leading |
Including support for |
Right, but I don't think that it is an problem to throw an exception if there is more than one leading dot. |
Sure, @MeiKatz. We can include support for one leading |
This lets people do stuff like <Route path="."> and <Route path="./users">. It's a little confusing because <Route path> can't ever have .. but there's a warning for that too. See discussion here: #5127 (comment)
We intend to ship Relative Routes and Relative Links soon. I'd like to hear people's comments on something I'm not totally sure about yet.
What are Relative Links and Routes?
Instead of
<Route path={match.path + '/more/stuff'}/>
you could simply do<Route path="more/stuff"/>
and Router will use the lack of/
at the start of your path as a hint that you want to just use whatever path is above you. Relative!Same with links. Instead of
<Link to={match.url + '/more/stuff'}/>
you can just do<Link to="more/stuff"/>
.The Problem
Going "down" as shown in the quick example up there is easy. But we're not sure what ".." should mean. There are two options for what it means: go up a URL segment, or go up a matched route.
Go up a URL segment
With plain anchors tags,
..
means "up a URL segment". Given the following URL, an anchor tag with the following HREFs results in the following URLs:payments
/clients/invoice/123/payments
/clients/invoice/123
.
/clients/invoice/123
..
/clients/invoice
../
/clients/invoice
../..
/clients
../../
/clients
(As far as I can tell, feel free to double check!)
So I think everybody's intuition around this is to just do exactly that and be done with it. Ship it.
However, I have this gut instinct that our intuition right now might be keeping us from a better meaning of ".." in a React Router app?
Go up a matched Route
Maybe your app is something like this:
Maybe we want ".." to mean "go up a matched route" rather than "go up some url segments".
So now the "relative" portion of the Link's
to
prop is the parent Route's path, not the current location...
/clients
../..
/
Notice
..
doesn't go to "invoice" because that's not a route that will ever match.Pros
path
prop, the relative links below don't need to be updatedCons
to
prop 😬. It all depends on the Route's path the Link is rendered within.What do you think?
The text was updated successfully, but these errors were encountered: