React-router: How to navigate outside of components in ReactRouter 4?

Created on 13 Jun 2017  路  17Comments  路  Source: ReactTraining/react-router

In React Router (v3) I can accept a server response and use browserHistory.push to go to the appropriate response page. However, this isn't available in v4, and I'm not sure what the appropriate way to handle this is in React-Router V4 .

In below example when the server returns a success, the user is taken to the Cart page

// actions/index.js
export function addProduct(props) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
      .then(response => {
        browserHistory.push('/cart'); // no longer in React Router V4
      });
}

How can I make a redirect to the page from function in React Router v4?

I can find lot of references on navigation from components in my case i need to navigate from a function

Most helpful comment

If I'm understanding the suggestion correctly, if we need to access the history object outside of a component we basically have to create our own version of BrowserRouter and have it use a history object we've created. Wouldn't it be easier on users of this package if those history objects were available like they used to be, rather than having to create a slightly modified version of something that's already done?

All 17 comments

@RamYadlapalli Here is one possible solution that I have found to work, although not ideal and definitely needs to be addressed in the core package. This is, of course, if you are using react-router-redux as well.

Given the following code, you can redirect to a different route by dispatching a push action.

import axios from 'axios'
import { push } from 'react-router-redux'
import { API_URL } from '../utils/apiUrl'

export const loginUser = creds => {
  return dispatch => {
    dispatch(loginRequest(creds))

    axios({
      method: "post",
      url: API_URL + "/auth_token",
      data: { auth: creds }
    }).then(res => {

       dispatch(push('/home')) /* dispatch an action that changes the browser history */ 

      })

    }).catch(err =>
      dispatch(loginFailure('Wrong email/password combination'))
    )
  }
}

I have looked and not found any other ways to do this yet. This becomes very messy because in your components, you will have callback functions on links if you want to change the route, like so:

<button className="btn btn-secondary btn-block"
    onClick={() => this.props.dispatch(push('/'))}>Home Page
</button>

If anyone has better ways of doing this, please share!

@anuragasaurus But if you are working _outside_ of a component, there is no history object as far as I can tell, no? I think that's a valid way of redirecting when working with components, but I don't see how that will work outside of a component.

@anuragasaurus When using react-router-redux, the suggested mention does not seem to work. The URL is changed, but the new component is not loaded because the redux state has not been updated. In order to change the state, you would have to dispatch an action as I have suggested.

@thisiserik yeah, i tried it without using react-router-redux and it worked.

@anuragasaurus But it doesn't work with react-router-redux, correct?

You can build your own history instance and pass it to <Router>. This is all that BrowserRouter and the others like it do: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js

If I'm understanding the suggestion correctly, if we need to access the history object outside of a component we basically have to create our own version of BrowserRouter and have it use a history object we've created. Wouldn't it be easier on users of this package if those history objects were available like they used to be, rather than having to create a slightly modified version of something that's already done?

No, because they were singletons and that was sort of an anti-pattern. We had to have escape hatches with useRouter(). When you import a module, you shouldn't be getting an instance, but a class/function/constant definition.

If you are outside of a route you can wrap your component in withRouter to get the history object.

I have multiple separate react apps on a page where each one can change the location and moving to RR4 obviously broke this. I made my own version of BrowserRouter that accepts a history prop and uses it if given. Otherwise it's identical. So now I can create a history outside of each app and pass it in to the respective CustomBrowserRouters.

@timdorr, would a PR with 鈽濓笍 that be welcome or do you not want people to be able to provide history? Am I missing something here that will come back to bite me?

No, we don't want people providing a history to the preconfigured Routers. Just use <Router history={myHistory}>.

Also, we're going to warn if you try to pass a history into the preconfigured Routers: #5151

Ok cool.

@wdjungst I tried so many ways and I found withRouter is the easiest and most reasonable way to reach the history object. Thank you!

if i want to nav to another path in Action, it's so difficult. I should create a history obj and pass it to then Action method.
any simple solution?

For v4: If you are using react-router-redux as well as redux-thunk and have the middlewares hooked up correctly, as well as have your app wrapped in

<Provider store={myStore}>
  <ConnectedRouter history={myHistory}>
    ...

--

You can import { push } from 'react-router-redux' and then dispatch it in a new thunk action. Let your redux container know about the action in mapDispatchToProps and then you can dispatch that action to navigate from any container or things containers pass it down to.

In your other actions, you can do async things and use that action to navigate to one screen or another depending on the result.

this lets you put your

<Switch>
  <Route />
  <Route />
</Switch>

wherever you like, nest them and also navigate from any container / action ( or pass the container props down further )

Just wrap the app in an extended BrowserRouter:

import { BrowserRouter } from 'react-router-dom';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

export default class YourBrowserRouter extends BrowserRouter {
  history;
}

And import { history } ... where you need it outside of react components. For me I wanted to redirect to the login page from within my fetch utility method if the server responded with a 401.

Was this page helpful?
0 / 5 - 0 ratings