React-router: Provide a way to use <Link> outside of router

Created on 18 Dec 2015  路  7Comments  路  Source: ReactTraining/react-router

I've been googling and reading documentation and chatting on discord, but with no success.

I've got an existing app to which I need to add, on home page, couple of 'Sign Up' buttons that should be <Link> to a react component (I have to avoid full page reload and provide page history). These buttons would just display a react component in an overlay. It fails since the <Link> is being used outside of the router.

Any way I could provide the context outside of router? If I just reinit the router for every button, the router throws warning saying it's already been initialized.

I've seen @Andrew1431 was searching for something similar. Any luck @Andrew1431?

My app uses rails as a backend and I'm not even sure how much work would it be to try and put everything in a react component (since it's not just pure static HTML, but has dynamic rails stuff in there as well).

Some solutions that come to my mind:

  1. Use plain old javascript to display an overlay and pushState to history (would this work with react router?)
  2. Figure out a way to add router to context for tag outside of router (this one is my favorite)
  3. Try to put the whole homepage inside a single master react component and then have the router available everywhere.

Most helpful comment

Hey there! This issue was really frustrating to figure out, but I finally got a hack work around in our system.

We're developing our system in a package-esque manner, meaning we have a global name space storing all our services / utilities and what not. I discovered through reading the source code of the Link object that all it is doing is preventing the default click action, and pushing state in to the history object. My hackish work around is as follows until there is a better way:

Where our routes are defined I included the history object in to the global name space.

/* Include react-layout browser history */
const browserHistory = history.createHistory();
Core.Utilities.History = browserHistory;

  render: function() {

    return (
      <Router history={browserHistory}>
        <Route path="/" component={App} onEnter={requireAuth}>
          <IndexRoute key="0" component={this.getDefaultRoute()}/>
          {this.state.routeService.Routes.map(function(r) {
            return r;
          })}
        </Route>
      </Router>
    );
  }

Then anywhere we needed Link elements out side of the router, I simply called the function the Link elements call:

let MenuItem = React.createClass({
  displayName: 'MenuItem',
  pushState(state) {
   Core.Utilities.History.pushState(null, state);
    closeMenu();
  },
  render() {
    let item = this.props.data;
    return <a className="item" onClick={() => {this.pushState(item.route)}}>{item.title}</a>
  }
});

Ideally I wish the Link element would take an optional history prop that allowed you to inject your own history reference rather than it looking for the contexts history which won't exist when the Link element is out side of the Route context (Our case was semantic UI's forcing the side menu to be out side of the application)

I hope this helps, sorry its not exactly what you're looking for, but it functions exactly as I need it to.

All 7 comments

Hey there! This issue was really frustrating to figure out, but I finally got a hack work around in our system.

We're developing our system in a package-esque manner, meaning we have a global name space storing all our services / utilities and what not. I discovered through reading the source code of the Link object that all it is doing is preventing the default click action, and pushing state in to the history object. My hackish work around is as follows until there is a better way:

Where our routes are defined I included the history object in to the global name space.

/* Include react-layout browser history */
const browserHistory = history.createHistory();
Core.Utilities.History = browserHistory;

  render: function() {

    return (
      <Router history={browserHistory}>
        <Route path="/" component={App} onEnter={requireAuth}>
          <IndexRoute key="0" component={this.getDefaultRoute()}/>
          {this.state.routeService.Routes.map(function(r) {
            return r;
          })}
        </Route>
      </Router>
    );
  }

Then anywhere we needed Link elements out side of the router, I simply called the function the Link elements call:

let MenuItem = React.createClass({
  displayName: 'MenuItem',
  pushState(state) {
   Core.Utilities.History.pushState(null, state);
    closeMenu();
  },
  render() {
    let item = this.props.data;
    return <a className="item" onClick={() => {this.pushState(item.route)}}>{item.title}</a>
  }
});

Ideally I wish the Link element would take an optional history prop that allowed you to inject your own history reference rather than it looking for the contexts history which won't exist when the Link element is out side of the Route context (Our case was semantic UI's forcing the side menu to be out side of the application)

I hope this helps, sorry its not exactly what you're looking for, but it functions exactly as I need it to.

That's about right - the semantics of <Link> don't really make sense outside of the router (how can it evaluate active state if it's not in a route at all?), but you can replicate the behavior yourself without too much trouble.

Thanks @Andrew1431!

I'm not that familiar with react. Can you help me figure out the first snippet? On which object is the first render method defined?

Should I require history from 'history' package?

/* Include react-layout browser history */
const browserHistory = history.createHistory();
Core.Utilities.History = browserHistory;

  render: function() {

    return (
      <Router history={browserHistory}>
        <Route path="/" component={App} onEnter={requireAuth}>
          <IndexRoute key="0" component={this.getDefaultRoute()}/>
          {this.state.routeService.Routes.map(function(r) {
            return r;
          })}
        </Route>
      </Router>
    );
  }

That should be the route object that is loaded in to the dom under react-routers docs. Here's the whole snippet we used.

/* Create the main Routes react component */
let Routes = React.createClass({
  /* ... */
  render: function() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={App} onEnter={requireAuth}>
          <IndexRoute key="0" component={this.getDefaultRoute()}/>
          {this.state.routeService.Routes.map(function(r) {
            return r;
          })}
        </Route>
      </Router>
    );
  }
});

Meteor.startup(function () {
  $(document).ready(function(){
    ReactDOM.render(<Routes />, document.getElementById('app'));
    ReactDOM.render(<SideNav />, document.getElementById('side-nav'));
  });
});

All though our system is designed for dynamic route loading so this is much more complicated than it needs to be :P I hope that helps a little bit. My custom link items are in the SideNav object out side of app element that I render Routes in to, and I'm too lazy to draw up an example to give you :)

  1. Get store your history object somewhere in the app
  2. call history.pushState(...) anywhere you want.

Thanks guys, it worked! :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ryansobol picture ryansobol  路  3Comments

nicolashery picture nicolashery  路  3Comments

maier-stefan picture maier-stefan  路  3Comments

jzimmek picture jzimmek  路  3Comments

stnwk picture stnwk  路  3Comments