Skip to content
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

Hash links #394

Closed
CezaryDanielNowak opened this issue Oct 12, 2014 · 70 comments · Fixed by thegazelle-ad/gazelle-server#217
Closed

Hash links #394

CezaryDanielNowak opened this issue Oct 12, 2014 · 70 comments · Fixed by thegazelle-ad/gazelle-server#217

Comments

@CezaryDanielNowak
Copy link

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

@mjackson
Copy link
Member

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.

@ryanflorence
Copy link
Member

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.

@BerkeleyTrue
Copy link

@rpflorence Is anyone working on this? I would be down to form a PR if not.

@ryanflorence
Copy link
Member

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 :)

@scabbiaza
Copy link

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">

@schickling
Copy link

Even with a starting slash it doesn't work for me. Reopen please.

@lostpebble
Copy link

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.

@juampynr
Copy link

Does not work for me either.

@whatupdave
Copy link

Yeh, this should be reopened. I'm using params to generate a more complex URL and need to add the hash.

@clementdubois
Copy link

Does not work for me either. This should be reopened please

@iyn
Copy link

iyn commented Jun 10, 2015

+1, I'm also interested in a solution.

@nickpresta
Copy link

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.

@mouhsinelonly
Copy link

+1

@Mythli
Copy link

Mythli commented Aug 24, 2015

Not working with Router.HistoryLocation. It just follows the link and does nothing.

@Pouja
Copy link

Pouja commented Aug 24, 2015

@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);
            }
        }
    }
});

@alansouzati
Copy link

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);
          }
        }
    }
});

@davis
Copy link

davis commented Oct 1, 2015

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

@knowbody
Copy link
Contributor

knowbody commented Oct 2, 2015

@davis in 1.0 there is nothing like scrollBehavior, you need to handle it yourself for now. we are working on it

@davis
Copy link

davis commented Oct 2, 2015

got it, thanks!

@alansouzati
Copy link

@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'} />
    )
  }
});

@dopeboy
Copy link

dopeboy commented Oct 15, 2015

@alansouzati, your solution worked great. Thank you.

@taion
Copy link
Contributor

taion commented Oct 15, 2015

1.0.0-rc3 actually has a hash prop on Link. You should use that.

@davis
Copy link

davis commented Oct 15, 2015

@taion how do you use it?

@taion
Copy link
Contributor

taion commented Oct 15, 2015

You pass the hash to it.

@davis
Copy link

davis commented Oct 15, 2015

nvm, it's still not working in 1.0.0-rc3 (#2216)

@alansouzati
Copy link

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.

@sanakr
Copy link

sanakr commented Aug 4, 2016

how to use <a href="mailto:[email protected]"/> in Link in react-router

@timdorr
Copy link
Member

timdorr commented Aug 4, 2016

@sanakr Just use a normal <a> element. Don't use Link.

@sanakr
Copy link

sanakr commented Aug 5, 2016

@timdorr i got solution, its possible in Link .

<Link to={{pathname:mailto:${value}}} className={style.qLink} target="_blank">{value}</Link> just like this.

@timdorr
Copy link
Member

timdorr commented Aug 5, 2016

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.

@Gijsjan
Copy link

Gijsjan commented Aug 30, 2016

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();
        }
      }
    >
  )
}

@amadeogallardo
Copy link

Is there any supported way to prevent re-rendering while using named anchors to sections on the same page? This causes an unnecessary overhead.

@kelchm
Copy link

kelchm commented Sep 20, 2016

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?

@ligaz
Copy link

ligaz commented Sep 30, 2016

@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.

@jk05
Copy link

jk05 commented Oct 9, 2016

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.

@kopax
Copy link

kopax commented Mar 2, 2017

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 ?

@abumalick
Copy link

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

@gajus
Copy link

gajus commented Jul 5, 2017

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

@huy-nguyen
Copy link

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.

@abumalick
Copy link

Yes, my solution is for react-router 3. With react-router 4 you should take history from props

@G2Jose
Copy link

G2Jose commented Nov 28, 2017

@RAFREX's solution (#394 (comment)) 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>

@lemmingapex
Copy link

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>

@remix-run remix-run locked as resolved and limited conversation to collaborators Mar 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet