Hey,
I'd like to write something like this:
<Link to="home#about">Home</Link>
It should open home
route and scroll to <section id="about"></section>
Will it be possible ?
Regards, Cezary Daniel Nowak
Hmm, good question.
We should definitely support this for people who are using <Routes location="history">
, which I assume you are. I think we probably need to add a <Link hash>
prop.
@rpflorence thoughts?
At first glance, it doesn't seem like using <Routes location="hash">
will work because it already uses window.location.hash
to manage the actual path. But we might be able to hack around it using a double hash, like #/home#about
. I think we're already doing this for query strings with hash location.
Yeah, I've always wanted this. Its part of the reason our history
fallback does page reloads, so that urls are consistent and you can still use anchor links as they are designed.
I don't want to try to support this in HashLocation
: we'd have to get involved with finding elements by ID or name and scrolling to them, etc. Additionally, those URLs are super weird looking.
But for HistoryLocation
it should be as simple as stripping off the #hash-stuff
before matching the route and then putting it back on when we use the history api.
@rpflorence Is anyone working on this? I would be down to form a PR if not.
Seems like this should already work if you've got HistoryLocation
. You can link to URLs with <Link to="/foo/bar#baz"/>
. Anything starting with a slash will just get passed through as the path you want. If it doesn't work, a failing test case would be fantastic :)
Yes, it works when you write in the to
attribute URL as a string.
But how to add hash if you are using route name?
Works:
<Route name="about-page" path="/about" handler={About}/>
<Link to="/about#hash"></Link>
Doesn't work:
<Route name="about-page" path="/about" handler={About}/>
<Link to="about-page#hash"></Link>
Error:
Uncaught Error: Invariant Violation: Unable to find <Route name="about-page#hash">
Even with a starting slash it doesn't work for me. Reopen please.
Yep, doesn't work for me either. Both ways. Shows up in the address bar as it should but never goes to the location on the page.
Does not work for me either.
Yeh, this should be reopened. I'm using params to generate a more complex URL and need to add the hash.
Does not work for me either. This should be reopened please
+1, I'm also interested in a solution.
As a workaround, in my main "app", I've added the following to componentDidMount
:
componentDidMount() {
// Decode entities in the URL
// Sometimes a URL like #/foo#bar will be encoded as #/foo%23bar
window.location.hash = window.decodeURIComponent(window.location.hash);
const scrollToAnchor = () => {
const hashParts = window.location.hash.split('#');
if (hashParts.length > 2) {
const hash = hashParts.slice(-1)[0];
document.querySelector(`#${hash}`).scrollIntoView();
}
};
scrollToAnchor();
window.onhashchange = scrollToAnchor;
}
This allows you to link to things with URLs like: /#foo#bar which will scroll to elements which have an id
attribute of bar
.
+1
Not working with Router.HistoryLocation. It just follows the link and does nothing.
@NickPresta solution did not work for me, but I could not figure out why. When you are switching from states it will not scroll, but if you refresh the page it will scroll to the element.
But I fix it by overriding the scrollBehavior with your solution.
So the config looks like this:
var router = Router.create({
routes: routes,
location: Router.HashLocation,
scrollBehavior: {
/**
* Sets the behaviour for scrolling when changing state.
* If there is a second hash in the url, scroll to the element with matching identifier.
* Otherwise scroll to the top.
* @override
*/
updateScrollPosition: function updateScrollPosition() {
window.location.hash = window.decodeURIComponent(window.location.hash);
const hashParts = window.location.hash.split('#');
if (hashParts.length > 2) {
const hash = hashParts.slice(-1)[0];
const element = document.querySelector(`#${hash}`);
if (element) {
element.scrollIntoView();
}
} else {
window.scrollTo(0, 0);
}
}
}
});
thanks @Pouja, your solution helped me to solve the issue I was facing. But I'm using HistoryLocation and for me I had to do the following:
var router = Router.create({
routes: routes,
location: Router.HistoryLocation,
scrollBehavior: {
updateScrollPosition: function updateScrollPosition() {
var hash = window.location.hash;
if (hash) {
var element = document.querySelector(hash);
if (element) {
element.scrollIntoView();
}
} else {
window.scrollTo(0, 0);
}
}
}
});
does this work in 1.0.0?
how can i configure this in jsx?
i have something like this:
React.render((
<Router onUpdate={() => window.scrollTo(0, 0)} scrollBehavior={{
/**
* Sets the behaviour for scrolling when changing state.
* If there is a second hash in the url, scroll to the element with matching identifier.
* Otherwise scroll to the top.
* @override
*/
updateScrollPosition: function updateScrollPosition() {
console.log('hi')
window.location.hash = window.decodeURIComponent(window.location.hash);
const hashParts = window.location.hash.split('#');
if (hashParts.length > 2) {
const hash = hashParts.slice(-1)[0];
const element = document.querySelector(`#${hash}`);
if (element) {
element.scrollIntoView();
}
} else {
window.scrollTo(0, 0);
}
}
}}>
<Route path="/" component={App}>
<IndexRoute component={LandingContent}/>
and updateScrollPosition isn't being called
@davis in 1.0 there is nothing like scrollBehavior
, you need to handle it yourself for now. we are working on it
got it, thanks!
@scabbiaza not sure if you were able to fix your issue, but if you want to use the route name you can invoke the makeHref
function from react router like this:
var Component = React.createClass({
contextTypes: {
router: React.PropTypes.func.isRequired
},
render: function() {
var aboutPathHref = this.context.router.makeHref('about-page');
return (
<Link to={aboutPathHref + '#hash'} />
)
}
});
@alansouzati, your solution worked great. Thank you.
1.0.0-rc3 actually has a hash
prop on Link
. You should use that.
@taion how do you use it?
You pass the hash to it.
nvm, it's still not working in 1.0.0-rc3 (#2216)
That's great to hear that react-router 1.0.0 supports that. But I believe some folks will need some time to migrate to react 0.14. In the meantime this work around will help people to get going.
@davis I thought the same, in the end also had to update history
to the latest version (currently 1.2.5
) and it all works as expected now, relates to https://github.com/rackt/history/commit/8d4ece5eedf93b22585518b2e1522884d0e26526 and https://github.com/rackt/history/issues/93
@adrianleb you mean 1.12.5 ?
Is there a resolution/example of this issue? I have a page that lists items alphabetically and I am trying to link down to a particular "letter". I have tried:
<Link hash='M' to='/'>M</Link>
<a name='M' id='M'></a>
to no avail. What am I missing? BTW. I am using createHashHistory
.
https://github.com/rackt/history/blob/master/docs/HashHistoryCaveats.md#caveats-of-using-hash-history
Can someone please document, step by step, how to get anchor links working with React-Router. Reading through the comments it is not clear.
+1
This doesn't act on react router directly, but as a workaround for the scrolling, I incorporated this function into my relevant component. It requires one to use createBrowserHistory
but the parsing could probably be done for createHashHistory
and still use the general idea:
scrollToAnchor: function () {
let anchorName = this.props.location.hash;
if (anchorName) {
anchorName = anchorName.replace("#","");
let anchorElement = document.getElementById(anchorName);
if(anchorElement) { anchorElement.scrollIntoView(); }
}
},
@alansouzati's answer works perfectly. Thank you!
What about using hash links with Dynamic routing? I'm getting Location "/create#name" did not match any routes
error when I add "#name" to "create" route, it doesn't even call the getChildRoutes()
function of "create" route when I add "#name". I would expect that the same route handler was called when I add some hash stuff at the end.
Edit: Ok, I just found out that if I use <Link to={{pathname: '/create', hash:'#name'}}>Create</Link>
it does takes me to /create#name
url when I click the link and the "create" route handle gets executed, but, if I type "/create#name" URL on the browser address bar and hit enter, react-router will throw the same error I mentioned before.
I met the same problem with @figalex ,but from my side,only when I hit the link,I got the error message as ''Warning: [react-router] Location "http://xxx#comment" did not match any routes,and if I click the link again,it generates a link like "http://xxx#comment#comment",both circumstances not react on scroll behavior
@solugebefola thanks for pointing out the .scrollIntoView()
method (MDN).
I'm able to scroll to an element on the page with just this:
scrollToResourcesPending = () => {
const {resourcesPending} = this.refs;
resourcesPending.scrollIntoView();
}
This doesn't bother with the URL bar, but I don't need an anchored route for my use case.
But what if you have content on your page that is loaded dynamically, inserted into your React-component via dangerouslySetInnerHtml, that includes anchors?
For example this might be loaded and inserted into some element in you component:
<a href="#somelink">
In standard HTML, this would jump to the a-tag with the name/id specified in the href-attribute</a>
<p>
Here's some general text or whatever. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin scelerisque at mi nec sodales. Aenean scelerisque vulputate sem sit amet ornare. Nullam a imperdiet sem, sit amet porta felis. Pellentesque lectus ante, ornare id turpis quis, interdum pharetra mi. Vivamus augue augue, bibendum eu ex sit amet, venenatis lobortis ex. Suspendisse at turpis consequat, cursus sapien ac, fermentum lorem. Duis id tortor leo. Praesent risus justo, consectetur a euismod et, feugiat in nibh. Nulla bibendum orci sed risus venenatis vulputate. Praesent libero orci, eleifend tincidunt ex eu, tincidunt mattis dui. In maximus fringilla justo et tempor. Nunc nec posuere purus. </p>
<a id="somelink">
The above link should jump here</a>
How would you handle this?
Update March 2017: this was originally a solution for RRv2/3, and has been adapted for RRv4 by creating a HashLink
component that you can install from npm.
Solution for React Router v4 (March 2017):
Live example: http://react-router-hash-link.rafrex.com
Repo: https://github.com/rafrex/react-router-hash-link
When you click on a link created with react-router-hash-link
it will scroll to the element on the page with with the id
that matches the #hash-fragment
in the link. Note that you must use React Router's BrowserRouter
for this to work.
$ npm install --save react-router-hash-link
// In YourComponent.js
...
import { HashLink as Link } from 'react-router-hash-link';
...
// Use it just like a RRv4 link:
<Link to="/some/path#with-hash-fragment">Link to Hash Fragment</Link>
Solution for React Router v2/3 (May 2016):
Repo: https://github.com/rafrex/react-router-hash-link/tree/react-router-v2/3
It uses a Router onUpdate
hook and calls element.scrollIntoView()
if a #hash
is present in the url. Since it's defined on the Router component, the scroll functionality will work with every route, and it works when linking to a #hash
on the current route, or when linking to a #hash
while navigating to a different route.
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
const routes = (
// your routes
);
function hashLinkScroll() {
const { hash } = window.location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}
render(
<Router
history={browserHistory}
routes={routes}
onUpdate={hashLinkScroll}
/>,
document.getElementById('root')
)
@rafrex thanks for this! works for me 馃憤
@rafrex Thanks for the solution. I've published to npm a solution based on that as a module called anchorate.
I have some code like this:
<Route name="about-page" path="/about" handler={About}/>
<Link to="/about#hash"></Link>
When I manually refresh my browser on /about#hash
, location.hash changed to an empty string "", I can't figure out why !
@vimniky Does the same thing happen with /about/#hash
(note the trailing slash)?
works great, thanks! @rafrex
I'm finding that onUpdate is called before the child components in my view have rendered. If I use a setTimeout
of sufficient length scrolling down the page works, but this obviously isn't a reasonable solution.
I'm currently using redux-connect, which I suspect might be what is causing this behavior. Anyone else out there using redux-async-connect?
@kelchm, I haven't used redux-connect, but I think what you're saying is that linking to the new url causes your app to fetch data from the server asynchronously, and only after the data loads is the element with the id corresponding to the #hash available on the page.
Can you use the hashLinkScroll
function as an on success callback to your async data fetch?
@rafrex, The problem is that I need a reliable way to know when the element with the provided ID has actually been rendered -- the data having been loaded does not tell me wether a component or it's children has rendered. Even using a combination of SetTimeout
and requestAnimationFrame
I was not able to get things working 100% reliably.
I've come up with a solution that, while inelegant, should _always_ work:
function hashLinkScroll(retryCount = 0, retryLimit = 300) {
console.debug('hashLinkScroll retry count: ', retryCount);
const { hash } = window.location;
if (hash !== '') {
window.requestAnimationFrame(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
else if (retryCount < retryLimit) setTimeout(hashLinkScroll(retryCount + 1), 100);
});
}
}
I've been using react-scroll and it's worked great plus it's got effects built into it. I figure on this one, why reinvent the wheel if it's already been done for something very lean like this...now if only react-router would use this under the hood, then we'd be all good.
It's my understanding that react-scroll doesn't support scrolling based on #hash-links
, is that not true?
I'm using browserHistory with the
Yea, I'm using browserHistory too. If I copy/paste a url like http://example.tld/foo/bar#some-id
will react-scroll scroll to the element on the page with id of some-id
? If so, do you have a code example? I had looked at react-scroll before but didn't think it supported this (none of the examples they provide use #hashes
in the url).
@rafrex here's an example of my code using it.
import {Component,Store} from 'reactivate';
var ReactDOM = require('react-dom'),
Scroll = require('react-scroll'),
DirectLink = Scroll.DirectLink,
Link = require('react-router').Link;
const TableOfContents = Component({
store: Store('/companies'),
contextTypes: {
router: React.PropTypes.func.isRequired
},
componentDidMount() {
const el = ReactDOM.findDOMNode(this);
new Ink.UI.Sticky(el,{offsetTop: 50, topElement: "#ft-company-profile", bottomElement: "#footer"});
},
render(){
return (
<div>
<p className="margin-8"><span className="blue medium"><DirectLink to='interviewee' spy={true} smooth={true} duration={500}>Interviewee</DirectLink></span></p>
</div>
)
}
})
Here is the component to which the to is looking for in terms of the hash:
const Interviewee = Component({
store: Store('/companies'),
render(){
var company = this.props.company,
interviewee = company.interview.interviewee,
pastCompanies = interviewee.pastCompanies.map(pastCompany =>
<span className="ink-badge black font-11">{pastCompany}</span>);
return (
<div id="ft-interviewee" className="all-100">
<p className="section-heading bold font-22" id="interviewee">Interviewee</p>
<div className="column-group horizontal-gutters">
<div className="all-10">
<div><img src={interviewee.portraitImage} /></div>
</div>
... rest of component code
Here's my route config, very simple so far, it's a new app
import { Component } from 'reactivate';
import { IndexRoute, Router, Route, hashHistory, browserHistory } from 'react-router'
import HomePage from './HomePage.js';
import Interview from './interview/Interview';
const Container = Component({
render: function () {
return (
<div>{this.props.children}</div>
);
}
});
const App = Component({
render() {
return (
<Router history={browserHistory} onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/">
<IndexRoute component={HomePage}/>
<Route name="interview" path="interviews/companies/:companyId" component={Interview}/>
</Route>
<Route path="/" component={Container}></Route>
</Router>
);
}
})
When I click the DirectLink it scrolls down or up to the interviewee section. So mine isn't going to a hash added lets say at the end of a full route definition... like mine's not going to /something/somethingelse#someAnchor. It's just looking for an anchor that exists on the same page.
When I click the DirectLink it scrolls down or up to the interviewee section. So mine isn't going to a hash added lets say at the end of a full route definition... like mine's not going to /something/somethingelse#someAnchor. It's just looking for an anchor that exists on the same page.
So if I understand you correctly, it's not scrolling based on the #hash
in the url. This is definitely useful for a lot of use cases (especially with the smooth transitions), but not if you need to save scroll position independent of the session, e.g. share a link like https://github.com/reactjs/react-router/issues/394#issuecomment-220221604
@rafrex ah I see yea, I don't have a usecase for that right now I get why you've created more to get this working.
how to use <a href="mailto:[email protected]"/>
in Link in react-router
@sanakr Just use a normal <a>
element. Don't use Link.
@timdorr i got solution, its possible in Link .
<Link to={{pathname:
mailto:${value}}} className={style.qLink} target="_blank">{value}</Link>
just like this.
You shouldn't do that. Just use a normal <a>
element.
<a href=`mailto:${value}` className={style.qLink} target="_blank">{value}</a>
Our <Link>
component isn't designed to take in URLs with different protocols and you are taking a performance hit that you don't need to be taking by adding the overhead of that component.
About hash links, I've had success with using a route param and the ref's callback. (so not really a hash link, but the same functionality)
{
items.map((item) =>
<li
ref={(el) => {
if (el != null && item.id === this.props.routeParams.id) {
el.scrollIntoView();
}
}
>
)
}
Is there any supported way to prevent re-rendering while using named anchors to sections on the same page? This causes an unnecessary overhead.
Is there any supported way to prevent re-rendering while using named anchors to sections on the same page? This causes an unnecessary overhead.
@Agalla81, have you tried using Link
with the full URI rather than just an anchor with a hash href?
@Agalla81 I'm successfully using the following _workaround_: override shouldComponentUpdate
in the route's component and return false
when you detect hash navigation.
Note: If your component is pure this should come out of the box.
I was wondering if there is now an accepted way to getting anchor links working with react-router? I have attempted many of the methods outlined and failed so had to revert to regular named anchors using id's. I couldn't find much on the internet about this. Would love to know more. Thanks.
@ligaz Would you happen to have an example that you could show me where you used this shouldComponentUpdate
workaround? Thank you.
I have tried @rafrex solution and it appear it work but the view get rerendered while it should not, is there a way to prevent this ?
A simple solution with https://www.npmjs.com/package/scroll-to-element that a friend found.
import { browserHistory, Link } from 'react-router';
import scrollToElement from 'scroll-to-element';
componentDidMount() {
this.jumpToHash();
}
componentDidUpdate() {
this.jumpToHash();
}
jumpToHash = () => {
const hash = browserHistory.getCurrentLocation().hash;
if (hash) {
scrollToElement(hash, { offset: -120 });
}
}
After that you add some div with an corresponding id
on the page
I did not like the @rafrex proposal because it requires to use custom Link
component. Here is a simple solution that achieves the same result but without requiring to override the Link
component:
https://medium.com/@gajus/making-the-anchor-links-work-in-spa-applications-618ba2c6954a
Just to add to @abumalick's solution, the history
object (which he/she imports as browserHistory
from react-router-dom
) is also available as this.props.history
on the Route
component. In that case, the hash
is available via this.props.history.hash
.
Yes, my solution is for react-router 3. With react-router 4 you should take history from props
@rafrex's solution (https://github.com/ReactTraining/react-router/issues/394#issuecomment-220221604) worked really well, but I didn't want to make such a top level change on a production website just in case there are regressions.
I only had one place where I needed this logic, so I used a ref instead that uses similar logic -
<a
href="#questionid"
id="questionid"
ref={element => {
const { hash } = window.location;
if (hash !== '') {
const id = hash.replace('#', '');
if (element.id === id) element.scrollIntoView();
}
}}
>
Text
</a>
Although it was not ideal, this worked out of the box for me with both HashRouter and BrowserRouter: https://github.com/rafrex/react-router-hash-link
import { HashLink } from "react-router-hash-link";
<HashLink to="/my-cool-page#my-cool-section">Goto Cool Section</HashLink>
<div id="my-cool-section">The coolest</div>
Most helpful comment
Update March 2017: this was originally a solution for RRv2/3, and has been adapted for RRv4 by creating a
HashLink
component that you can install from npm.Solution for React Router v4 (March 2017):
Live example: http://react-router-hash-link.rafrex.com
Repo: https://github.com/rafrex/react-router-hash-link
When you click on a link created with
react-router-hash-link
it will scroll to the element on the page with with theid
that matches the#hash-fragment
in the link. Note that you must use React Router'sBrowserRouter
for this to work.Solution for React Router v2/3 (May 2016):
Repo: https://github.com/rafrex/react-router-hash-link/tree/react-router-v2/3
It uses a Router
onUpdate
hook and callselement.scrollIntoView()
if a#hash
is present in the url. Since it's defined on the Router component, the scroll functionality will work with every route, and it works when linking to a#hash
on the current route, or when linking to a#hash
while navigating to a different route.