React-router: Way to use both historylocation, and hash navigation?

Created on 4 Feb 2015  ·  11Comments  ·  Source: ReactTraining/react-router

I want to use historylocation for my overall application, but I want the ability to use hashes in one of the components... is this currently possible?

Most helpful comment

I found a hacky solution to this. I essentially copied the HistoryCollection location, and modified the getWindowPath to include the hash:

function getWindowPath() {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  var hash = window.location.href.split('#')[1] || ''
  if(hash) hash = "#" + hash

  return decodeURI(
    window.location.pathname + window.location.search + hash
  );
}

So react-router considers the hash when making routing decisions. Then I can define routes like this:

var routes = (
  <Route handler={App}>
    <Route name="buildList" path="/widgets" handler={require("./components/WidgetList")} />
    <Route name="show" path="/widget/:widget" handler={require("./components/WidgetShow")} />
    <Route name="showJob" path="/widget/:widget#:job" handler={require("./components/WidgetShow")} />
    <Route name="showJobAndLine" path="/widget/:widget#:job/:line" handler={require("./components/WidgetShow")} />
  </Route>
);

So the routes with the anchors just like any other route - and we can still use all the nice things that react-router supplies. @gaearon can you think of an easier way of achieving this behaviour instead of me copying HistoryLocation and changing getWindowPath?

All 11 comments

hashes will continue to do what hashes are designed to do in the first place, link to anchors on the page. What are you trying to do exactly?

In the documentation, it's mentioned that react-router can either be used like this:

Router.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

which basically defaults to "Router.HashLocation"

Or like this:

Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});

Which uses html5pushstate.

The thing is, I want to use both. Most of my application maps to how the server would render it, but there is a portion of my application that is completely client side, and I don't want to put an "actual" url window.location that if the user copy and pasted, would just hit a 404. Do you get what I'm saying? I want the ability on a parent component to use HistoryLocation, and inside a child component inside the routes argument, I want to define a separate route, and use HashLocation.

Following is sample code:

var routes = (
    <Route name="App" path="/" handler={AppComponent}>
        <Route name="main" path="main/" handler={MainComponent} />
        <Route name="checkout" path="main/checkout/" handler={Checkout} />
    </Route>
);

Router.run(
    routes,
    Router.HistoryLocation,
    function(Handler) {
        React.render(<Handler />,document.getElementById('app-container'));
    }
);

So this would be a normal application (not perfect but you get the general idea.)

But let's say inside of checkout I have a persistent header, and then different parts of checkout

var routes = (
    <Route name="App" path="/main/checkout" handler={CheckoutForm}>
        <Route name="main" path="main/checkout/shipping" handler={CheckoutShipping} />
        <Route name="checkout" path="main/checkout/billing" handler={CheckoutBilling} />
    </Route>
);


Router.run(
    routes,
    function(Handler) {
        React.render(<Handler />,document.getElementById('app-container'));
    }
);

Again, the above isn't perfectly coded (hence the reason I'm asking), but you see what I'm getting at? A component in the parent route, maybe wants to have it's own routes, that don't use HistoryLocation. So take into consideration the following urls:

http://www.example.com
http://www.example.com/main
http://www.example.com/main/checkout
http://www.example.com/main/checkout#shipping
http://www.example.com/main/checkout#billing

So like I said, combining the uses of the hashes (hashstate) and history location. If you use HistoryLocation the whole way through and whatever urls the router is generating don't match up with the response of the server, if the url is copy pasted, it's going to be a problem. But with the hashes, that's a safe(r) link to share.

I can clarify any of this, let me know.

tl;dr is navigation completely client side from the starting example url up above to the last url at the bottom possible with react-router

Why not fix the server to correctly handle these “client-side” routes? You'd only need it to serve checkout on checkout/*. Or am I missing the point?

Changing server isn't always possible. Not a deal breaker, I just wanted to see if this was possible with this library... I'm assuming it's not at this point :smile:

I guess I also have this inclination where I think that, due to everyones collective experience on the web improving, people have recognized that sometimes hashes appear in the browser and sometimes (or often) they might be ignored. Rewriting an actual url seems a little bit more like a redirect (it sort of is, whereas the hash will happily sit there).

Also... you could use the hashes to let people skip around the process (theoretically). So someone could come in and immediately go to http://www.example.com/main/checkout#billing (which doesn't make too much sense in the checkout case but just saying it might be useful somewhere, even given query strings).

In any case, it's fine. Just wanted to know if it was possible, no worries.

I wouldn't say it's impossible, I think you can implement a custom Location that uses both HashLocation and HistoryLocation under the hood.

Any luck with the custom location approach?

I'll have to look into it. Thanks for the tip.

I guess I'm supposed to close this ticket now. Thanks again.

No problem. If you're having troubles, let me know.

I found a hacky solution to this. I essentially copied the HistoryCollection location, and modified the getWindowPath to include the hash:

function getWindowPath() {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  var hash = window.location.href.split('#')[1] || ''
  if(hash) hash = "#" + hash

  return decodeURI(
    window.location.pathname + window.location.search + hash
  );
}

So react-router considers the hash when making routing decisions. Then I can define routes like this:

var routes = (
  <Route handler={App}>
    <Route name="buildList" path="/widgets" handler={require("./components/WidgetList")} />
    <Route name="show" path="/widget/:widget" handler={require("./components/WidgetShow")} />
    <Route name="showJob" path="/widget/:widget#:job" handler={require("./components/WidgetShow")} />
    <Route name="showJobAndLine" path="/widget/:widget#:job/:line" handler={require("./components/WidgetShow")} />
  </Route>
);

So the routes with the anchors just like any other route - and we can still use all the nice things that react-router supplies. @gaearon can you think of an easier way of achieving this behaviour instead of me copying HistoryLocation and changing getWindowPath?

(The use case is a feature similar to the line linking on GitHub). So in the case of GitHub, you'd have like a FileShow component, and you'd be able to define an anchor in the route, and be able to highlight the line using the react state gear.

Has a solution similar to @keithpitt 's above been identified as a possible/correct way to do this.
I have just a different use-case in that I think it just more sense to use /paths/ for things on different views and # hashes for things on the same view but under a different subview...eg. tabs, carousel, etc etc. Just seems to make more sense, even if a completely /path/ solution works all the same.

Was this page helpful?
0 / 5 - 0 ratings