169

I'm new to ReactJS and to React-Router. 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>
         ...
      );
   }
});
...
Alan Souza
  • 7,095
  • 9
  • 42
  • 65

9 Answers9

282

React Router v6 - React 17+ (updated 01/14/2022)

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
  • 4,963
  • 1
  • 19
  • 21
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,495
  • 9
  • 54
  • 88
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
8

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,607
  • 17
  • 16
  • 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. – Lancelot Jun 19 '17 at 14:02
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,095
  • 9
  • 42
  • 65
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
  • 50,085
  • 28
  • 134
  • 178
  • 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
2

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 })
JonWik
  • 21
  • 2
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,181
  • 13
  • 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
  • 269
  • 2
  • 6