185

I have a component that receives through props a <Link/> object from react-router. Whenever the user clicks on a 'next' button inside this component I want to invoke <Link/> object manually.

Right now, I'm using refs to access the backing instance and manually clicking on the 'a' tag that <Link/> generates.

Question: Is there a way to manually invoke the Link (e.g. this.props.next.go)?

This is the current code I have:

//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
<Document next={sampleLink} />

//in Document.js
...
var Document = React.createClass({
   _onClickNext: function() {
      var next = this.refs.next.getDOMNode();
      next.querySelectorAll('a').item(0).click(); //this sounds like hack to me
   },
   render: function() {
      return (
         ...
         <div ref="next">{this.props.next} <img src="rightArrow.png" onClick={this._onClickNext}/></div>
         ...
      );
   }
});
...

This is the code I would like to have:

//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
<Document next={sampleLink} />

//in Document.js
...
var Document = React.createClass({
   render: function() {
      return (
         ...
         <div onClick={this.props.next.go}>{this.props.next.label} <img src="rightArrow.png" /> </div>
         ...
      );
   }
});
...
starball
  • 20,030
  • 7
  • 43
  • 238
Alan Souza
  • 7,475
  • 10
  • 46
  • 68

9 Answers9

317

React Router v6.8 - React 18+ (updated 02/24/2023)

import React from 'react';
import {useNavigate} from 'react-router-dom';

export default function StackOverflowExample() {
  const navigate = useNavigate();
  const handleOnClick = () => navigate('/sample');

  return (
    <button type="button" onClick={handleOnClick}>
      Go home
    </button>
  );
}

Docs: https://reactrouter.com/en/main/hooks/use-navigate

React Router v6 - React 17+

import React, {useCallback} from 'react';
import {useNavigate} from 'react-router-dom';

export default function StackOverflowExample() {
  const navigate = useNavigate();
  const handleOnClick = useCallback(() => navigate('/sample', {replace: true}), [navigate]);

  return (
    <button type="button" onClick={handleOnClick}>
      Go home
    </button>
  );
}

Note: For this answer, the one major change between v6 and v5 is useNavigate is now the preferred React hook. useHistory is deprecated and not recommended.

React Router v5 - React 16.8+ with Hooks

If you're leveraging React Hooks, you can take advantage of the useHistory API that comes from React Router v5.

import React, {useCallback} from 'react';
import {useHistory} from 'react-router-dom';

export default function StackOverflowExample() {
  const history = useHistory();
  const handleOnClick = useCallback(() => history.push('/sample'), [history]);

  return (
    <button type="button" onClick={handleOnClick}>
      Go home
    </button>
  );
}

Another way to write the click handler if you don't want to use useCallback

const handleOnClick = () => history.push('/sample');

React Router v4 - Redirect Component

The v4 recommended way is to allow your render method to catch a redirect. Use state or props to determine if the redirect component needs to be shown (which then trigger's a redirect).

import { Redirect } from 'react-router';

// ... your class implementation

handleOnClick = () => {
  // some action...
  // then redirect
  this.setState({redirect: true});
}

render() {
  if (this.state.redirect) {
    return <Redirect push to="/sample" />;
  }

  return <button onClick={this.handleOnClick} type="button">Button</button>;
}

Reference: https://reacttraining.com/react-router/web/api/Redirect

React Router v4 - Reference Router Context

You can also take advantage of Router's context that's exposed to the React component.

static contextTypes = {
  router: PropTypes.shape({
    history: PropTypes.shape({
      push: PropTypes.func.isRequired,
      replace: PropTypes.func.isRequired
    }).isRequired,
    staticContext: PropTypes.object
  }).isRequired
};

handleOnClick = () => {
  this.context.router.push('/sample');
}

This is how <Redirect /> works under the hood.

Reference: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Redirect.js#L46,L60

React Router v4 - Externally Mutate History Object

If you still need to do something similar to v2's implementation, you can create a copy of BrowserRouter then expose the history as an exportable constant. Below is a basic example but you can compose it to inject it with customizable props if needed. There are noted caveats with lifecycles, but it should always rerender the Router, just like in v2. This can be useful for redirects after an API request from an action function.

// browser router file...
import createHistory from 'history/createBrowserHistory';
import { Router } from 'react-router';

export const history = createHistory();

export default class BrowserRouter extends Component {
  render() {
    return <Router history={history} children={this.props.children} />
  }
}

// your main file...
import BrowserRouter from './relative/path/to/BrowserRouter';
import { render } from 'react-dom';

render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>
);

// some file... where you don't have React instance references
import { history } from './relative/path/to/BrowserRouter';

history.push('/sample');

Latest BrowserRouter to extend: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js

React Router v2

Push a new state to the browserHistory instance:

import {browserHistory} from 'react-router';
// ...
browserHistory.push('/sample');

Reference: https://github.com/reactjs/react-router/blob/master/docs/guides/NavigatingOutsideOfComponents.md

Matt Lo
  • 5,442
  • 1
  • 21
  • 21
  • 7
    hashHistory.push('/sample'); if you are using hashHistory instead of browserHistory – sanath_p Dec 21 '16 at 22:40
  • 1
    this is especially useful in the material-ui library as using containerElement={} doesn't always invoke the link – Vishal Disawar Jan 28 '17 at 06:07
  • I believe browserHistory is going away in react-router v4 – James Gentes Apr 14 '17 at 19:10
  • @JamesGentes I updated this to reflect v4 changes. I just recently migrated and use v4 for web and react native. Let me know if you see any issues. – Matt Lo Apr 15 '17 at 21:43
  • 3
    Note with the redirect option you must specify push (ie ). By default it will do a replace which is not at all the same as manually invoking a Link – aw04 May 03 '17 at 15:28
  • I just wanted to add an "active" css to the Link - I dont want to refactor the whole thing. :( – jokab Sep 19 '17 at 02:27
  • 1
    @jokab you can use instead of https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js – Matt Lo Sep 19 '17 at 03:57
  • 1
    Redirect is not working for me, but aw04 solution with withRouter is more simple and working – stackdave Jun 10 '18 at 05:35
  • After redirect, this.state.redirect remain true and instead of button display, it is null component as redirect do not render any DOM. – Sany Liew Oct 04 '18 at 16:18
  • if you use react hooks, you can consider useHistory hook – dfang Aug 19 '20 at 07:54
  • I updated the answer with the latest Hooks impl. This is the standard I use day-to-day on my own projects – Matt Lo Sep 23 '20 at 16:38
  • This answer is outdated for v6, see below https://stackoverflow.com/a/70137765/2052752 – Carson Wood Jan 09 '22 at 06:17
  • answered updated for v6! – Matt Lo Jan 14 '22 at 22:01
  • updated answer for 2023 standards... the cool thing about this post is you can also see the evolution of React. I haven't used `PropTypes` since 2017! – Matt Lo Feb 24 '23 at 19:49
95

React Router 4 includes a withRouter HOC that gives you access to the history object via this.props:

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom'

class Foo extends Component {
  constructor(props) {
    super(props)

    this.goHome = this.goHome.bind(this)
  }

  goHome() {
    this.props.history.push('/')
  }

  render() {
    <div className="foo">
      <button onClick={this.goHome} />
    </div>
  }
}

export default withRouter(Foo)
aw04
  • 10,857
  • 10
  • 56
  • 89
  • 10
    This worked for me and it looks like most simple solution. – Rubycut Jun 27 '17 at 12:29
  • 6
    This is the best solution. I don't nderstand why it has so few votes. – Benoit Nov 05 '17 at 21:50
  • 1
    yes, you can click on link few times and browser back won't work. you'll need to click on browser back for a few times to really go back – Vladyslav Tereshyn Mar 04 '19 at 10:42
  • 1
    @VladyslavTereshyn you can add some conditional logic: if ((this.props.location.pathname + this.props.location.search) !== navigateToPath) { ... } – MattWeiler Apr 20 '20 at 14:16
  • after searching for one hour, this solution finally worked. Thank you! – IonicMan Aug 17 '21 at 15:47
  • But can someone explain why this code will not work in the mother component (App.js)? – IonicMan Aug 17 '21 at 15:48
  • @IonicMan I believe it won't work in `App.js` because the `this.props.history` object is only available to components being displayed under a ``. – M - Nov 05 '21 at 05:30
25

In the version 5.x, you can use useHistory hook of react-router-dom:

// Sample extracted from https://reacttraining.com/react-router/core/api/Hooks/usehistory
import { useHistory } from "react-router-dom";

function HomeButton() {
  const history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}
Paulo Mateus
  • 456
  • 6
  • 10
  • This is the best solution. If you add some conditional logic, you can avoid duplicate entries in the history when a user clicks the same button multiple times: `if ((routerHistory.location.pathname + routerHistory.location.search) !== navigateToPath) { routerHistory.push(navigateToPath); }` – MattWeiler Apr 20 '20 at 15:37
  • 1
    I needed to declare `history` variable, directory invoking `useHistory().push` is not allowed by hooks rules – onmyway133 Jun 02 '20 at 16:00
  • this seems the most modern react-ishy solution. – Youngjae Jul 30 '20 at 09:50
  • This is neat and off-course works for v5.x With arrow functions one could simply further as `onClick={ () => history.push('/home') }` – KeshavDulal Sep 23 '20 at 06:37
10

https://github.com/rackt/react-router/blob/bf89168acb30b6dc9b0244360bcbac5081cf6b38/examples/transitions/app.js#L50

or you can even try executing onClick this (more violent solution):

window.location.assign("/sample");
grechut
  • 2,897
  • 1
  • 19
  • 18
  • As lines of code change, your answer will be better if you copy the details and explain your answer here. Also, `assign` is not a property, it's a function. – WiredPrairie Mar 25 '15 at 10:54
  • (But you still just have a link to a specific line of a file). Please include the specific suggestion in your answer, and not just a link. – WiredPrairie Mar 25 '15 at 15:29
  • Thanks for your answer @grechut. However, I want to make sure Document does not know anything about router at all. The behavior I'm expecting is: 'If the user clicks in the right arrow, invoke the next function'. The next function may be a link or not. – Alan Souza Mar 25 '15 at 17:10
  • I have a couple of pages handled outside of React (login screens with FB and Google redirects) so I needed this in the nav for those pages since "browserHistory.push('/home');" only changed the URL, it was unable to route the pages. Thank you. – Deborah Jan 10 '17 at 12:51
  • 7
    This would reload the page @grechut, not a desired behavior for an application with react router. – Abhas Feb 08 '17 at 15:46
  • As many already said, this doesn't really work with Router. It will refresh the whole page. – Roberto Jun 19 '17 at 14:02
4

Answers here are outdated.

React Router 6

useHistory is deprecated v6 uses the useNavigate hook instead.

import { useNavigate } from 'react-router-dom'

const navigate = useNavigate()

navigate(`/somewhere`, { replace: true })
Staz
  • 151
  • 5
2

Ok, I think I was able to find a proper solution for that.

Now, instead of sending <Link/> as prop to Document, I send <NextLink/> which is a custom wrapper for the react-router Link. By doing that, I'm able to have the right arrow as part of the Link structure while still avoiding to have routing code inside Document object.

The updated code looks like follows:

//in NextLink.js
var React = require('react');
var Right = require('./Right');

var NextLink = React.createClass({
    propTypes: {
        link: React.PropTypes.node.isRequired
    },

    contextTypes: {
        transitionTo: React.PropTypes.func.isRequired
    },

    _onClickRight: function() {
        this.context.transitionTo(this.props.link.props.to);
    },

    render: function() {
        return (
            <div>
                {this.props.link}
                <Right onClick={this._onClickRight} />
            </div>  
        );
    }
});

module.exports = NextLink;

...
//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
var nextLink = <NextLink link={sampleLink} />
<Document next={nextLink} />

//in Document.js
...
var Document = React.createClass({
   render: function() {
      return (
         ...
         <div>{this.props.next}</div>
         ...
      );
   }
});
...

P.S: If you are using the latest version of react-router you may need to use this.context.router.transitionTo instead of this.context.transitionTo. This code will work fine for react-router version 0.12.X.

Alan Souza
  • 7,475
  • 10
  • 46
  • 68
2

React Router 4

You can easily invoke the push method via context in v4:

this.context.router.push(this.props.exitPath);

where context is:

static contextTypes = {
    router: React.PropTypes.object,
};
Chris
  • 54,599
  • 30
  • 149
  • 186
  • Using `BrowserRouter`, my components' context object does't contain a `router` object. Am I doing anything wrong? – pilau Mar 01 '17 at 17:54
  • Are you setting the context in the component (the second block above)? – Chris Mar 02 '17 at 00:00
  • Thanks for chiming in! eventually this worked for me: `router: React.PropTypes.object.isRequired`. I don't know why it didn't work without the `isRequired` key. Also, `` seems to be able to get the `history` context, but I couldn't replicate it. – pilau Mar 02 '17 at 13:36
  • Interesting one - if you put up a codepen I could help you debug it if you are still stuck – Chris Mar 02 '17 at 13:49
  • It seems like you can use `this.props.history.push()` in React Router v4. I only found this by inspecting the props that React Router passes in though. It seems to work but I'm not sure if it's a good idea. – sean_j_roberts Mar 16 '17 at 14:51
0

If you'd like to extend the Link component to utilise some of the logic in it's onClick() handler, here's how:

import React from 'react';
import { Link } from "react-router-dom";

// Extend react-router-dom Link to include a function for validation.
class LinkExtra extends Link {
  render() {
    const linkMarkup = super.render();
    const { validation, ...rest} = linkMarkup.props; // Filter out props for <a>.
    const onclick = event => {
      if (!this.props.validation || this.props.validation()) {
        this.handleClick(event);
      } else {
        event.preventDefault();
        console.log("Failed validation");
      }
    }

    return(
      <a {...rest} onClick={onclick} />
    )
  }
}

export default LinkExtra;

Usage

<LinkExtra to="/mypage" validation={() => false}>Next</LinkExtra>
Hedley Smith
  • 1,307
  • 15
  • 12
-1

again this is JS :) this still works ....

var linkToClick = document.getElementById('something');
linkToClick.click();

<Link id="something" to={/somewhaere}> the link </Link>
taggartJ
  • 277
  • 2
  • 6