Home » Reactjs » How to push to History in React Router v4?

How to push to History in React Router v4?

Posted by: admin November 30, 2017 Leave a comment

Questions:

In the current version of 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 this example, using Redux, components/app-product-form.js calls this.props.addProduct(props) when a user submits the form. 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 => {
        dispatch({ type: types.AUTH_USER });
        localStorage.setItem('token', response.data.token);
        browserHistory.push('/cart'); // no longer in React Router V4
      });
}

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

Answers:

React Router v4 is fundamentally different from v3 (and earlier) and you cannot do browserHistory.push() like you used to.

This discussion seems related if you want more info:

  • Creating a new browserHistory won’t work because <BrowserRouter> creates its own history instance, and listens for changes on that. So a different instance will change the url but not update the <BrowserRouter>.
  • browserHistory is not exposed by react-router in v4, only in v2.

Instead you have a few options to do this:

  • Use the withRouter high-order component

    Instead you should use the withRouter high order component, and wrap that to the component that will push to history. For example:

    import React from "react";
    import {withRouter} from "react-router-dom";
    
    class MyComponent extends React.Component {
      ...
      myFunction() {
        this.props.history.push("/some/Path");
      }
      ...
    }
    export default withRouter(MyComponent);
    

    Check out the official documentation for more info:

    You can get access to the history object’s properties and the closest <Route>‘s match via the withRouter higher-order component. withRouter will re-render its component every time the route changes with the same props as <Route> render props: { match, location, history }.


  • Use the context API

    Using the context might be one of the easiest solutions, but being an experimental API it is unstable and unsupported. Use it only when everything else fails. Here’s an example:

    import React from "react";
    import PropTypes from "prop-types";
    
    class MyComponent extends React.Component {
      static contextTypes = {
        router: PropTypes.object
      }
      constructor(props, context) {
         super(props, context);
      }
      ...
      myFunction() {
        this.context.router.history.push("/some/Path");
      }
      ...
    }
    

    Have a look at the official documentation on context:

    If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.

    If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it’s easier to upgrade when the API changes.

Questions:
Answers:

You can use history methods outside of your components. Try by the following way.

First, create a history object used the HTML5 history API:

// src/history.js

import createHistory from 'history/createBrowserHistory';

export default createHistory();

Then wrap it in <Router> (note that you should use import { Router } instead of import { BrowserRouter as Router }):

// src/index.jsx

// ...
import { Router, Link, Route } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/login">Login</Link></li>
        </ul>

        <Route exact path="/" component={Home} />
        <Route path="/login" component={Login} />
      </div>
    </Router>
  </Provider>,
  document.getElementById('root'),
);

Change the current location in any action, for example:

// src/actions/sessionActions.js

// ...
import history from '../history';

export function login(credentials) {
  return function (dispatch) {
    return SessionApi.login(credentials)
      .then((response) => {
        // ...
        history.push('/');
        // ...
      }).catch((error) => {
        throw error;
      });
  };
}

UPD: You can also see a slightly different example in React Router FAQ.

Questions:
Answers:

It is how I did that:

import React, {Component} from 'react';

export default class Link extends Component {
    constructor(props) {
        super(props);
        this.onLogout = this.onLogout.bind(this);
    }
    onLogout() {
        this.props.history.push('/');
    }
    render() {
        return (
            <div>
                <h1>Your Links</h1>
                <button onClick={this.onLogout}>Logout</button>
            </div>
        );
    }
}

Use this.props.history.push('/cart'); to redirect to cart page it will be saved in history object.

Enjoy, Michael.

Questions:
Answers:

this.context.history.push will not work.

I managed to get push working like this:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

    if (this.props.auth.success) {
        this.context.router.history.push("/some/Path")
    }

}

Questions:
Answers:

In this case you’re passing props to your thunk. So you can simply call

props.history.push('/cart')

If this isn’t the case you can still pass history from your component

export function addProduct(data, history) {
  return dispatch => {
    axios.post('/url', data).then((response) => {
      dispatch({ type: types.AUTH_USER })
      history.push('/cart')
    })
  }
}

Questions:
Answers:

According to React Router v4 documentation – Redux Deep Integration session

Deep integration is needed to:

“be able to navigate by dispatching actions”

However, they recommend this approach as an alternative to the “deep integration”:

“Rather than dispatching actions to navigate you can pass the history object provided to route components to your actions and navigate with it there.”

So you can wrap your component with the withRouter high order component:

export default withRouter(connect(null, { actionCreatorName })(ReactComponent));

which will pass the history API to props. So you can call the action creator passing the history as a param. For example, inside your ReactComponent:

onClick={() => {
  this.props.actionCreatorName(
    this.props.history,
    otherParams
  );
}}

Then, inside your actions/index.js:

export function actionCreatorName(history, param) {
  return dispatch => {
    dispatch({
      type: SOME_ACTION,
      payload: param.data
    });
    history.push("/path");
  };
}

Questions:
Answers:

Nasty question, took me quite a lot of time, but eventually, I solved it next way:

Wrap your container with withRouter and pass history to your action in mapDispatchToProps function. In action use history.push(‘/url’) to navigate.

Action:

export function saveData(history, data) {
  fetch.post('/save', data)
     .then((response) => {
       ...
       history.push('/url');
     })
};

Container:

import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));

This is valid for React Router v4.x.

Questions:
Answers:

If you are using Redux, then I would recommend using npm package react-router-redux. It allows you to dispatch Redux store navigation actions.

You have to create store as described in their Readme file.

The easiest use case:

import { push } from 'react-router-redux'

this.props.dispatch(push('/second page'));

Second use case with Container/Component:

Container:

import { connect } from 'react-redux';
import { push } from 'react-router-redux';

import Form from '../components/Form';

const mapDispatchToProps = dispatch => ({
  changeUrl: url => dispatch(push(url)),
});

export default connect(null, mapDispatchToProps)(Form);

Component:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Form extends Component {
  handleClick = () => {
    this.props.changeUrl('/secondPage');
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}/>
      </div>Readme file
    );
  }
}

Questions:
Answers:

I offer one more solution in case it is worthful for someone else.

I have a history.js file where I have the following:

import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history

Next, on my Root where I define my router I use the following:

import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'

export default class Root extends React.Component {
  render() {
    return (
     <Provider store={store}>
      <Router history={history}>
       <Switch>
        ...
       </Switch>
      </Router>
     </Provider>
    )
   }
  }

Finally, on my actions.js I import History and make use of pushLater

import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)

This way, I can push to new actions after API calls.

Hope it helps!

Questions:
Answers:

Here’s my hack (this is my root-level file, with a little redux mixed in there – though I’m not using react-router-redux):

const store = configureStore()
const customHistory = createBrowserHistory({
  basename: config.urlBasename || ''
})

ReactDOM.render(
  <Provider store={store}>
    <Router history={customHistory}>
      <Route component={({history}) => {
        window.appHistory = history
        return (
          <App />
        )
      }}/>
    </Router>
  </Provider>,
  document.getElementById('root')
)

I can then use window.appHistory.push() anywhere I want (for example, in my redux store functions/thunks/sagas, etc) I had hoped I could just use window.customHistory.push() but for some reason react-router never seemed to update even though the url changed. But this way I have the EXACT instance react-router uses. I don’t love putting stuff in the global scope, and this is one of the few things I’d do that with. But it’s better than any other alternative I’ve seen IMO.

Questions:
Answers:

I was able to accomplish this by using bind(). I wanted to click a button in index.jsx, post some data to the server, evaluate the response, and redirect to success.jsx. Here’s how I worked that out…

index.jsx:

import React, { Component } from "react"
import { postData } from "../../scripts/request"

class Main extends Component {
    constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
        this.postData = postData.bind(this)
    }

    handleClick() {
        const data = {
            "first_name": "Test",
            "last_name": "Guy",
            "email": "[email protected]"
        }

        this.postData("person", data)
    }

    render() {
        return (
            <div className="Main">
                <button onClick={this.handleClick}>Test Post</button>
            </div>
        )
    }
}

export default Main

request.js:

import { post } from "./fetch"

export const postData = function(url, data) {
    // post is a fetch() in another script...
    post(url, data)
        .then((result) => {
            if (result.status === "ok") {
                this.props.history.push("/success")
            }
        })
}

success.jsx:

import React from "react"

const Success = () => {
    return (
        <div className="Success">
            Hey cool, got it.
        </div>
    )
}

export default Success

So by binding this to postData in index.jsx, I was able to access this.props.history in request.js… then I can reuse this function in different components, just have to make sure I remember to include this.postData = postData.bind(this) in the constructor().