We have a use case with live sport event with multiple tabs each displaying different aspects of the event, such as statistics, a detailed timeline, a summary of the event etc. We want each of these tabs to be reachable with distinct URLs and switching between them should update the URL. However, during the course of a live event, the user might switch between the tabs repeatedly. We're using <Link/> to display the tabs, since we want the URL to update when such a switch occurs. But pushing to the history for each tab switch makes the history quite bloated and we'd rather just replace the history entry instead. This can of course be achieved using an onClick-listener. But the <Link/> has a lot of great functionality in its default click handler, such as invariant checking, checking if the event has been prevented etc. This is functionality we'd like to retain (_preferably without having to duplicate it in the new onClick-handler_)
pushing to the history, or replace the current history entry.For option 1
<Link to="..." action="replace"/>
We add a prop (named appropriately) where the user can indicate the name of the method to call. The default value would be "push". Inside the default onClick-listener we'd use this prop to call the corresponding method on context.router.
For option 2
const onNavigate = (history, locationDescriptor) => history.replace(locationDescriptor);
<Link to="..." onNavigate={onNavigate}/>
We add a prop which the <Link/> calls instead of pushing to the history if it is set. By providing history and the location descriptor, we let the user take full responsibility for completing the navigation.
I'd say this is such a common use case and thus like the simplicity of the first implementation suggestion.
Dupe https://github.com/reactjs/react-router/pull/2914, https://github.com/reactjs/react-router/issues/3618, https://github.com/reactjs/react-router/pull/3763.
We intentionally want to keep <Link> as minimal as possible, to parallel as closely as possible what an <a> does in the browser. We understand that this excludes legitimate use cases, but the concern here is to optimize for a11y out-of-the-box.
Instead, have you considered extracting the relevant logic from <Link> such that it can be used elsewhere? I do something like this in react-router-bootstrap (which doesn't support this feature).
Note that if you're doing this, you probably want to set something like role="button" on the link component.
_First off_, thanks for the quick reply!
_Secondly_, sorry for not looking through the past issues more thoroughly first!
_Thirdly_, given that I can find a neat way to properly extract the relevant logic from <Link/> and make it sharable with a new component <ReplaceLink/> (or whatever it should be called), would you be interested in a PR and support this use-case inside of react-router, or would you rather keep it outside? Given all the considerations that needs to be taken into account (thanks for the heads-up), as well as the possibility to share code more efficiently if implemented by react-router, I'm quite sure that it would be appreciated if a component that "does it right"â„¢ would be officially supported.
On the downside though, it makes the API-surface of react-router bigger.
We'd prefer not to have <ReplaceLink> in React Router proper.
Totally fine, I get that.
Does that mean you also close the door to providing some kind of template-method API where users could benefit from the logic in the default click handler (isLeftClick, isModifiedEvent, isDefaultPrevented, do we fulfill context invariants) but conclude the navigation in another way? Replicating this logic and keeping it up to date as React Router changes seems a bit... wasteful.
I don't know. If you want to, take a look at <LinkContainer> in react-router-bootstrap. It'd be great if I could remove as much of the duplicated logic from there as possible. It's just that, at present, neither something like <LinkContainer> nor <ReplaceLink> seem like good fits for this library.
https://github.com/react-bootstrap/react-router-bootstrap/blob/master/src/LinkContainer.js
I just wrote my own wrapper to replace the route instead of push.
import { Link as ReactRouterLink, browserHistory } from 'react-router';
const handleReplaceClick = (onClick, toRoute) => {
onClick();
browserHistory.replace(toRoute);
};
const toRender = (props, onClick) => {
if (props.action === 'PUSH') {
return (
<ReactRouterLink
{...props}
onClick={onClick}
/>
);
} else if (props.action === 'REPLACE') {
return (
<div onClick={handleReplaceClick.bind(null, onClick, props.to)}>
{props.children}
</div>
);
}
return null;
};
// Wrapper around React Router's Link
export const Link = (props) => {
const handleOnClick = isToCheckoutRoute ? setRoute : props.onClick;
return toRender(props, props.onClick);
};
Link.defaultProps = {
action: 'PUSH',
};
then you can just:
import { Link } from '../<path-to-helper>';
<Link to"/path_to_replace" action="REPLACE"> click me, i replace the link instead. </Link>
Most helpful comment
I just wrote my own wrapper to
replacethe route instead ofpush.then you can just: