360

I have just replaced react-router from v3 to v4.
But I am not sure how to programmatically navigate in the member function of a Component. i.e in handleClick() function I want to navigate to /path/some/where after processing some data. I used to do that by:

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')

But I can't find such interfaces in v4.
How can I navigate using v4?

Rubens Mariuzzo
  • 25,735
  • 25
  • 111
  • 145
Colin Wang
  • 5,988
  • 5
  • 21
  • 39

17 Answers17

429

If you are targeting browser environments, you need to use react-router-dom package, instead of react-router. They are following the same approach as React did, in order to separate the core, (react) and the platform specific code, (react-dom, react-native ) with the subtle difference that you don't need to install two separate packages, so the environment packages contain everything you need. You can add it to your project as:

yarn add react-router-dom

or

npm i react-router-dom

The first thing you need to do is to provide a <BrowserRouter> as the top most parent component in your application. <BrowserRouter> uses the HTML5 history API and manages it for you, so you don't have to worry about instantiating it yourself and passing it down to the <BrowserRouter> component as a prop (as you needed to do in previous versions).

In V4, for navigating programatically you need to access the history object, which is available through React context, as long as you have a <BrowserRouter> provider component as the top most parent in your application. The library exposes through context the router object, that itself contains history as a property. The history interface offers several navigation methods, such as push, replace and goBack, among others. You can check the whole list of properties and methods here.

Important Note to Redux/Mobx users

If you are using redux or mobx as your state management library in your application, you may have come across issues with components that should be location-aware but are not re-rendered after triggering an URL update

That's happening because react-router passes location to components using the context model.

Both connect and observer create components whose shouldComponentUpdate methods do a shallow comparison of their current props and their next props. Those components will only re-render when at least one prop has changed. This means that in order to ensure they update when the location changes, they will need to be given a prop that changes when the location changes.

The 2 approaches for solving this are:

  • Wrap your connected component in a pathless <Route />. The current location object is one of the props that a <Route> passes to the component it renders
  • Wrap your connected component with the withRouter higher-order component, that in fact has the same effect and injects location as a prop

Setting that aside, there are four ways to navigate programatically, ordered by recommendation:

1.- Using a <Route> Component

It promotes a declarative style. Prior to v4, <Route /> components were placed at the top of your component hierarchy, having to think of your routes structure beforehand. However, now you can have <Route> components anywhere in your tree, allowing you to have a finer control for conditionally rendering depending on the URL. Route injects match, location and history as props into your component. The navigation methods (such as push, replace, goBack...) are available as properties of the history object.

There are 3 ways to render something with a Route, by using either component, render or children props, but don't use more than one in the same Route. The choice depends on the use case, but basically the first two options will only render your component if the path matches the url location, whereas with children the component will be rendered whether the path matches the location or not (useful for adjusting the UI based on URL matching).

If you want to customise your component rendering output, you need to wrap your component in a function and use the render option, in order to pass to your component any other props you desire, apart from match, location and history. An example to illustrate:

import { BrowserRouter as Router } from 'react-router-dom'

const ButtonToNavigate = ({ title, history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    {title}
  </button>
);

const SomeComponent = () => (
  <Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)    

const App = () => (
  <Router>
    <SomeComponent /> // Notice how in v4 we can have any other component interleaved
    <AnotherComponent />
  </Router>
);

2.- Using withRouter HoC

This higher order component will inject the same props as Route. However, it carries along the limitation that you can have only 1 HoC per file.

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

const ButtonToNavigate = ({ history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    Navigate
  </button>
);


ButtonToNavigate.propTypes = {
  history: React.PropTypes.shape({
    push: React.PropTypes.func.isRequired,
  }),
};

export default withRouter(ButtonToNavigate);

3.- Using a Redirect component

Rendering a <Redirect> will navigate to a new location. But keep in mind that, by default, the current location is replaced by the new one, like server-side redirects (HTTP 3xx). The new location is provided by to prop, that can be a string (URL to redirect to) or a location object. If you want to push a new entry onto the history instead, pass a push prop as well and set it to true
<Redirect to="/your-new-location" push />

4.- Accessing router manually through context

A bit discouraged because context is still an experimental API and it is likely to break/change in future releases of React
const ButtonToNavigate = (props, context) => (
  <button
    type="button"
    onClick={() => context.router.history.push('/my-new-location')}
  >
    Navigate to a new location
  </button>
);

ButtonToNavigate.contextTypes = {
  router: React.PropTypes.shape({
    history: React.PropTypes.object.isRequired,
  }),
};

Needless to say there are also other Router components that are meant to be for non browser ecosystems, such as <NativeRouter> that replicates a navigation stack in memory and targets React Native platform, available through react-router-native package.

For any further reference, don't hesitate to take a look at the official docs. There is also a video made by one of the co-authors of the library that provides a pretty cool introduction to react-router v4, highlighting some of the major changes.

basse
  • 634
  • 1
  • 8
  • 27
rgommezz
  • 11,676
  • 2
  • 16
  • 22
  • It seems that `v4` does not have `withRouter` – Colin Wang Feb 09 '17 at 06:59
  • 2
    I am using V4 and the above works fine. I have spent a fair bit of time mucking about with the V4 router as there seems some strange choices but the above definitely works. I assume you are importing `withRouter` from `react-router-dom` – Sam Parmenter Feb 09 '17 at 10:14
  • As Sam states, I was gonna point that out, just updated my answer to make that clear – rgommezz Feb 09 '17 at 10:37
  • 4
    I am importing `withRouter` from `react-router-dom`. The history.push does change the url, but it does not seem to load the `` until I force refresh the page ... – BreakDS Mar 20 '17 at 06:44
  • Could you share some code through a repo or snippet? – rgommezz Mar 20 '17 at 12:32
  • 1
    @rauliyohmc I was wrong about this. The problem is that I have `` inside a React component who is decorated with `@observer`, which triggers [this issue](https://github.com/mobxjs/mobx-react/issues/210). The work around is to have `@withRouter` on every such React component. – BreakDS Mar 20 '17 at 14:42
  • That's right @BreakDS, the same happens with redux `@connect` decorator. I updated my answer to reflect that, since it seems to be a common issue right now for new users. – rgommezz Mar 21 '17 at 00:04
  • 3
    For those who come across this great answer, `withRouter` is simply a HOC that uses a `Route` under the hood. Which means that it only uses [3 props, history, match, and location](https://reacttraining.com/react-router/web/api/Route/Route-props). In the example above it appears that `push` is a prop that `withRouter` would add to `ButtonToNavigate`, but that is not the case. `props.history.push` would have to be used instead. Hope this helps others who were a bit confused. – vinnymac Mar 23 '17 at 22:21
  • Great catch, my original response was based on the alpha version, in which `withRouter` received different props. I see that changed in the final official release, will update to avoid further confusions :) – rgommezz Mar 23 '17 at 22:47
  • In the first example, you say that "you can pass to Route any other props you desire, so that your component will receive them.". In the latest production version 4.0 of react-router this does not seem to be the case. Based on my tests, and the documentation (https://reacttraining.com/react-router/web/api/Route/Route-props) the only three properties that get passed to the component are match, location, and history. – Todd Chaffee Mar 24 '17 at 22:24
  • There is a way to work that around, and that is using the `render` prop in a ``, take a look at the updated 1st example – rgommezz Mar 25 '17 at 19:21
  • 42
    Wow. `browserHistory.push('/path/some/where')` just seems to much simpler. The authors are trying to discourage imperative programming, but sometimes it just works better! – lux Apr 22 '17 at 17:27
  • thanks for that answer - it should be a part of docs in my opinion – apieceofbart May 20 '17 at 20:38
  • 1
    That note about wrapping the connected component in a Route is so incredibly helpful. – Catalyst May 29 '17 at 00:55
  • The easy way is to use the histroy. this.props.history.push('/some/path') – Hiroto Oct 30 '17 at 08:03
  • Could you please update the code in this post? PropTypes cannot be found anymore in React main package, would need (after npm install prop-types): import PropTypes from 'prop-types'; and history: PropTypes.shape({ push: PropTypes.func.isRequired, }) – Braulio Aug 05 '18 at 16:34
  • The video link is dead. Any chance it's still available somewhere? – basse Oct 12 '18 at 12:12
  • Btw `PropTypes` for history object injected using `withRouter` decorator doesn't work. Throws undefined for the whole history – JSEvgeny Dec 17 '18 at 22:14
160

The easiest way to get it done:

this.props.history.push("/new/url")

Note:

  • You may want to pass the history prop from parent component down to the component you want to invoke the action if its not available.
Jikku Jose
  • 16,638
  • 10
  • 35
  • 55
  • 10
    I use 4.0 router, but there is no history key on props. How can I fix it? – Nicolas S.Xu Dec 01 '17 at 21:09
  • 54
    If you don't have `this.props.history` available in your component then you can `import {withRouter} from 'react-router-dom'` and then `export default withRouter(MyComponent)` (or `const MyComponent = withRouter(...)`) and it will insert the `history` item in your component's props. – Malvineous Feb 28 '18 at 07:08
  • @Malvineous thats interesting, didn't know about this! Will try it! – Jikku Jose Feb 28 '18 at 15:39
  • 2
    I must be missing something fundamental, because this works just great for me so I have no idea why all the answers require such lengthy explanations. – Joshua Pinter May 17 '18 at 22:07
  • how to prevent component remount on `history.push` and just trigger update like when we click on `` – Rahul Yadav May 23 '18 at 19:41
  • The best answer was: this.props.history.push("/new/url") – Jackkobec Apr 11 '19 at 23:48
  • The confusion comes from the fact that this only really works in a Class based approach. It doesn't work quite the same way in a functional approach because `this` isn't bound and props aren't accessible in that way. In a functional approach, you just need to start at the `history` object and use `history.push('/path')`. – Software Engineer Dec 23 '20 at 11:05
56

I had a similar issue when migrating over to React-Router v4 so I'll try to explain my solution below.

Please do not consider this answer as the right way to solve the problem, I imagine there's a good chance something better will arise as React Router v4 becomes more mature and leaves beta (It may even already exist and I just didn't discover it).

For context, I had this problem because I occasionally use Redux-Saga to programmatically change the history object (say when a user successfully authenticates).

In the React Router docs, take a look at the <Router> component and you can see you have the ability to pass your own history object via a prop. This is the essence of the solution - we supply the history object to React-Router from a global module.

Steps:

  1. Install the history npm module - yarn add history or npm install history --save
  2. create a file called history.js in your App.js level folder (this was my preference)

    // src/history.js
    
    import createHistory from 'history/createBrowserHistory';
    export default createHistory();`
    
  3. Add this history object to your Router component like so

    // src/App.js
    
    import history from '../your/path/to/history.js;'
    <Router history={history}>
    // Route tags here
    </Router>
    
  4. Adjust the URL just like before by importing your global history object:

    import history from '../your/path/to/history.js;'
    history.push('new/path/here/');
    

Everything should stay synced up now, and you also have access to a way of setting the history object programmatically and not via a component/container.

Alex Mann
  • 722
  • 6
  • 11
  • 5
    This change worked for me, but only because I'm navigating outside of a component. If I was navigating inside a component like the OP, I would use the approach suggested by @rauliyohmc by using the props passed down by the `Route` component. – Dan Ellis Mar 15 '17 at 12:01
  • 2
    this is the recommended approach as of 08/17 – Spets Aug 18 '17 at 06:02
  • 2
    @Spets In my case, if I use this kind of approach, the link will be updated properly after the push, but the components are not rendered properly (eg, after updating the link, component is not updated unless you force page refresh). Where did you find that this is recommended approach? Any links/sources? – cool Aug 23 '17 at 14:53
  • @cool I was just reading this page on blocking issues with redux, have you seen it? https://reacttraining.com/react-router/core/guides/dealing-with-update-blocking – Scott Coates Aug 25 '17 at 16:58
  • 2
    @ScottCoates I sorted out using the example above, indeed by providing the history as a parameter, but after I did debug of the node modules by myself. There is a common mistake made all around the web, using import of "BrowserHistory as Router", when instead there is another object called Router by itself in the latest version of the react-router-dom. Using that in combination with history created in example above works just fine. – cool Aug 25 '17 at 18:19
  • 1
    @cool this is all over the github issues page as the solution. Glad you're able to solve it, there are a few quarks with the migration and snags but yeah its from react-router the low level library and not react-router-dom – Spets Aug 30 '17 at 07:44
  • Man..., #4 was the one i was looking for, as i my Meteor rendered page had multiple React Roots, and i was going crazy on how to trigger navigation from a component inside the second root. This is just perfect. Days of frustration are gone.. – Shyam Nov 13 '17 at 08:24
  • 2
    the url is updated, but the page is not renderd based on the new root. Any solution? Why trigger route is so difficult? People who design react is out of their mind? – Nicolas S.Xu Dec 01 '17 at 21:22
  • 2
    Same, location bar is updating to new path but nothing is being rendered. – Ryan Jan 22 '18 at 22:42
47

TL;DR:

if (navigate) {
  return <Redirect to="/" push={true} />
}

The simple and declarative answer is that you need to use <Redirect to={URL} push={boolean} /> in combination with setState()

push: boolean - when true, redirecting will push a new entry onto the history instead of replacing the current one.


import { Redirect } from 'react-router'

class FooBar extends React.Component {
  state = {
    navigate: false
  }

  render() {
    const { navigate } = this.state

    // here is the important part
    if (navigate) {
      return <Redirect to="/" push={true} />
    }
   // ^^^^^^^^^^^^^^^^^^^^^^^

    return (
      <div>
        <button onClick={() => this.setState({ navigate: true })}>
          Home
        </button>
      </div>
    )
  }
}

Full example here. Read more here.

PS. The example uses ES7+ Property Initializers to initialise state. Look here as well, if you're interested.

Lyubomir
  • 17,533
  • 4
  • 51
  • 65
  • 5
    This should be accepted answer. Simplest elegant solution! +1 for @lustoykov – Ali Abbas Jaffri Sep 15 '17 at 12:36
  • 1
    I also set navigate back to false in componentDidUpdate as my button is in a header and would otherwise only navigate once. – peterorum Sep 20 '17 at 12:32
  • This only works if you know about the redirect when the page loads. If you're waiting for an asynchronous call to return (i.e. authentication with Google or something) then you'll have to use one of the `history.push()` methods. – brittohalloran Jun 20 '18 at 11:13
  • Not really, you can still leverage the declarative nature of react combined with the component. If the page does not load, you can fallback to another – Lyubomir Jun 22 '18 at 16:59
  • @brittohalloran the idea of this method using the proper react router, is to make sure you use setState to force a re-render. – Someone Special Nov 28 '18 at 06:10
  • Pretty sure you need to set navigate to false afterwards otherwise it will try to redirect multiple times (depending on your component structure of course) – Keno Clayton Jun 30 '19 at 21:13
34

Use useHistory hook if you're using function components

You can use useHistory hook to get history instance.

import { useHistory } from "react-router-dom";

const MyComponent = () => {
  const history = useHistory();
  
  return (
    <button onClick={() => history.push("/about")}>
      Click me
    </button>
  );
}

The useHistory hook gives you access to the history instance that you may use to navigate.

Use history property inside page components

React Router injects some properties including history to page components.

class HomePage extends React.Component {
  render() {
    const { history } = this.props;

    return (
      <div>
        <button onClick={() => history.push("/projects")}>
          Projects
        </button>
      </div>
    );
  }
}

Wrap child components withRouter to inject router properties

withRouter wrapper injects router properties to components. For example you can use this wrapper to inject router to logout button component placed inside user menu.

import { withRouter } from "react-router";

const LogoutButton = withRouter(({ history }) => {
  return (
    <button onClick={() => history.push("/login")}>
      Logout
    </button>
  );
});

export default LogoutButton;
TheMisir
  • 2,761
  • 1
  • 17
  • 30
11

You can also simply use props to access history object: this.props.history.push('new_url')

TheMisir
  • 2,761
  • 1
  • 17
  • 30
user1445685
  • 405
  • 6
  • 16
  • 5
    Useful only when in the component directly descended from the Router. Lest you pass the history prop to every single component you need this functionality in. – mwieczorek Sep 21 '17 at 14:34
  • 3
    If you don't have `this.props.history` available in your component then you can `import {withRouter} from 'react-router-dom'` and then `export default withRouter(MyComponent)` (or `const MyComponent = withRouter(...)`) and it will insert the `history` item in your component's props. – Malvineous Feb 28 '18 at 07:04
8

Step 1: There is only one thing to import on top:

import {Route} from 'react-router-dom';

Step 2: In your Route, pass the history:

<Route
  exact
  path='/posts/add'
  render={({history}) => (
    <PostAdd history={history} />
  )}
/>

Step 3: history gets accepted as part of props in the next Component, so you can simply:

this.props.history.push('/');

That was easy and really powerful.

TheMisir
  • 2,761
  • 1
  • 17
  • 30
cdi-meo
  • 134
  • 1
  • 3
6

My answer is similar to Alex's. I'm not sure why React-Router made this so needlessly complicated. Why should I have to wrap my component with a HoC just to get access to what's essentially a global?

Anyway, if you take a look at how they implemented <BrowserRouter>, it's just a tiny wrapper around history.

We can pull that history bit out so that we can import it from anywhere. The trick, however, is if you're doing server-side rendering and you try to import the history module, it won't work because it uses browser-only APIs. But that's OK because we usually only redirect in response to a click or some other client-side event. Thus it's probably OK to fake it:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}

With the help of webpack, we can define some vars so we know what environment we're in:

plugins: [
    new DefinePlugin({
        '__SERVER__': 'false',
        '__BROWSER__': 'true', // you really only need one of these, but I like to have both
    }),

And now you can

import history from './history';

From anywhere. It'll just return an empty module on the server.

If you don't want use these magic vars, you'll just have to require in the global object where it's needed (inside your event handler). import won't work because it only works at the top-level.

mpen
  • 237,624
  • 230
  • 766
  • 1,119
6

This works:

import { withRouter } from 'react-router-dom';

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;
Kaya Toast
  • 4,631
  • 6
  • 30
  • 52
5

I think that @rgommezz covers most of the cases minus one that I think it's quite important.

// history is already a dependency or React Router, but if don't have it then try npm install save-dev history

import createHistory from "history/createBrowserHistory"

// in your function then call add the below 
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");

This allows me to write a simple service with actions/calls that I can call to do the navigation from any component I want without doing a lot HoC on my components...

It is not clear why nobody has provided this solution before. I hope it helps, and if you see any issue with it please let me know.

4

I've been testing v4 for a few days now and .. I'm loving it so far! It just makes sense after a while.

I also had the same question and I found handling it like the following worked best (and might even be how it is intended). It uses state, a ternary operator and <Redirect>.

In the constructor()

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);

In the render()

render(){
    return (
        <div>
        { this.state.redirectTo ?
            <Redirect to={{ pathname: this.state.redirectTo }} /> : 
            (
             <div>
               ..
             <button onClick={ this.clickhandler } />
              ..
             </div>
             )
         }

In the clickhandler()

 this.setState({ redirectTo: '/path/some/where' });

Hope it helps. Let me know.

jar0m1r
  • 725
  • 4
  • 8
  • Are there any pitfalls with this method? In my project with works only when I set state in constructor, from there I can redirect to any page I want. But when I set state on event (props did change, for example) I see render method called with new state, but redirect don't happen I see same page – Dmitry Malugin Apr 21 '17 at 09:50
  • Pitfall is that it will replace the history, so you won't be able to hit back - so basically, this is not a good solution. – dannytenaglias Sep 06 '17 at 19:03
4

I struggled with this for a while - something so simple, yet so complicated, because ReactJS is just a completely different way of writing web applications, it's very alien to us older folk!

I created a separate component to abstract the mess away:

// LinkButton.js

import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';

export default class LinkButton extends React.Component {

    render() {
        return (
            <Route render={({history}) => (
                <button {...this.props}
                       onClick={() => {
                           history.push(this.props.to)
                       }}>
                    {this.props.children}
                </button>
            )}/>
        );
    }
}

LinkButton.propTypes = {
    to: PropTypes.string.isRequired
};

Then add it to your render() method:

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>
mwieczorek
  • 1,759
  • 2
  • 25
  • 33
3

Since there's no other way to deal with this horrible design, I wrote a generic component that uses the withRouter HOC approach. The example below is wrapping a button element, but you can change to any clickable element you need:

import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';

const NavButton = (props) => (
  <Button onClick={() => props.history.push(props.to)}>
    {props.children}
  </Button>
);

NavButton.propTypes = {
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }),
  to: PropTypes.string.isRequired
};

export default withRouter(NavButton);

Usage:

<NavButton to="/somewhere">Click me</NavButton>
rodrigocfd
  • 2,904
  • 3
  • 16
  • 33
3
this.props.history.push("/url")

If you have not found this.props.history available in your component , then try this

import {withRouter} from 'react-router-dom'
export default withRouter(MyComponent)  
Shashwat Gupta
  • 3,139
  • 23
  • 21
2

As sometimes I prefer to switch routes by Application then by buttons, this is a minimal working example what works for me:

import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'

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

    /** @type BrowserRouter */
    this.router = undefined
  }

  async handleSignFormSubmit() {
    await magic()
    this.router.history.push('/')
  }

  render() {
    return (
      <Router ref={ el => this.router = el }>
        <Link to="/signin">Sign in</Link>
        <Route path="/signin" exact={true} render={() => (
          <SignPage onFormSubmit={ this.handleSignFormSubmit } />
        )} />
      </Router>
    )
  }
}
piotr_cz
  • 5,837
  • 1
  • 26
  • 22
2

You can navigate conditionally by this way

import { useHistory } from "react-router-dom";

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

  function handleClick() {
    history.push("/path/some/where");
  }

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

For those of you who require to redirect before fully initalizing a router using React Router or React Router Dom You can provide a redirect by simply accesing the history object and pushing a new state onto it within your constructur of app.js. Consider the following:

function getSubdomain(hostname) {
    let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
    let urlParts = regexParse.exec(hostname);
    return hostname.replace(urlParts[0], '').slice(0, -1);
}

class App extends Component {

    constructor(props) {
        super(props);


        this.state = {
            hostState: true
        };

        if (getSubdomain(window.location.hostname).length > 0) {
            this.state.hostState = false;
            window.history.pushState('', '', './login');
        } else {
            console.log(getSubdomain(window.location.hostname));
        }

    }


    render() {
        return (

            <BrowserRouter>
                {this.state.hostState ? (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                        <Route path="/" component={PublicContainer}/>
                    </div>
                ) : (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                    </div>
                )

                }
            </BrowserRouter>)
    }


}

Here we want to change the output Routes dependant on a subdomain, by interacting with the history object before the component renders we can effectively redirect while still leaving our routes in tact.

window.history.pushState('', '', './login');
li x
  • 3,392
  • 2
  • 26
  • 45