75

I'm using react-redux and react-router. I need to redirect after an action is dispatched.

For example: I have registration a few steps. And after action:

function registerStep1Success(object) {
    return {
        type: REGISTER_STEP1_SUCCESS,
        status: object.status
   };
}

I want to redirect to page with registrationStep2. How can I do this?

p.s. In history browser '/registrationStep2' has not been visited. This page appears only after successful result registrationStep1 page.

Rich
  • 5,316
  • 9
  • 35
  • 58
ximet
  • 1,048
  • 2
  • 9
  • 15
  • 1
    react-router-dom v4 solution can be found here: https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4 – Ryan O'Donnell Oct 16 '18 at 01:46

9 Answers9

65

With React Router 2+, wherever you dispatch the action, you can call browserHistory.push() (or hashHistory.push() if that’s what you use):

import { browserHistory } from 'react-router'

// ...
this.props.dispatch(registerStep1Success())
browserHistory.push('/registrationStep2')

You can do this from async action creators too if that is what you use.

Dan Abramov
  • 241,321
  • 75
  • 389
  • 492
  • 1
    what are the benefits using redux-router in future, which now in beta? – ximet Mar 01 '16 at 12:58
  • 6
    If you ever want to have Redux DevTools replay routing transitions you’d need to choose between https://github.com/acdlite/redux-router and https://github.com/reactjs/react-router-redux. In this case I would recommend https://github.com/reactjs/react-router-redux because it is more stable and much simpler. – Dan Abramov Mar 01 '16 at 13:20
  • @DanAbramov how can we do a redirect after an action as ximet is asking? is that correct to do a "push" in the reducer that handles REGISTER_STEP1_SUCCESS? – pedrommuller Feb 20 '17 at 20:55
  • 2
    Is this solution still available? I don't seem to be able to get this working... The URL updates after I use `browserHistory.push()` but the view does not. – vasekhlav Mar 09 '17 at 16:00
  • 1
    Nevermind, I got it working, I was using `browserHistory.push()` although my router used `hashHistory`. `hashHistory.push()` works like a charm. – vasekhlav Mar 09 '17 at 16:10
  • @vasekhlav Im glad I found your comment above. Use hashHistory if the store is using it. – Juni Brosas Mar 14 '17 at 14:55
  • 14
    Does this still hold true for React Router 4+? Or is there now a better way to do this? – Joerg Feb 22 '18 at 10:38
  • Tip: If you want to redirect (without pushing location to history) like `````` just use ```history.replace('/someroute')```. – Artur Jul 10 '18 at 10:27
  • `browserHistory` no longer works, but there is [a possibility to create your own history object](https://stackoverflow.com/a/45849608/5763764) and access it from anywhere. – Radek Matěj Sep 18 '19 at 13:02
  • I know its for sake of example but you really shouldn't separate registration part 1 and 2 into 2 routes or 2 seperate forms unless you are doing it for the sake of email verification or some other purpose like that. Furthermore the browserHistory no longer works in new version of react, now they are suggesting to use the Redirect component which is extended from react-router-dom or the useHistory hook. I hate using hooks so i stick with Redirect and browserHistory when im working with older versions of react. – Dimitar Veljanovski May 29 '20 at 08:41
  • For me even the import { browserHistory } from 'react-router' is not working. 'browserHistory' is not exported from 'react-router' – azamsharp Dec 03 '20 at 14:09
25

Have you checked out react-router-redux? This library makes it possible to sync react-router with redux.

Here is an example from the documentation of how you can implement the redirection with a push action from react-router-redux.

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

// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
  reducers,
  applyMiddleware(middleware)
)

// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))
Eni Arinde
  • 487
  • 4
  • 5
7

Simplest solution for router version 4+: We use "react-router-dom": "4.3.1" It doesn't work with version 5+

export your browser history from the place where it was initialised and use browserHistory.push('/pathToRedirect'):

Package history must be installed(example: "history": "4.7.2"):

npm install --save history

In my project I initialise browser history in index.js:

import { createBrowserHistory } from 'history';

export const browserHistory = createBrowserHistory();

Redirect in the action:

export const actionName = () => (dispatch) => {
    axios
            .post('URL', {body})
            .then(response => {
                // Process success code
                  dispatch(
                    {
                      type: ACTION_TYPE_NAME,
                      payload: payload
                    }
                  );
                }
            })
            .then(() => {
                browserHistory.push('/pathToRedirect')
            })
            .catch(err => {
                // Process error code
                    }
                );
            });
};
Jackkobec
  • 3,665
  • 23
  • 22
  • It does change the `url`, but doesn't effects the 'react-router'. If you're using this module - I'll advice to look for a different solution. – YanivGK Jun 16 '20 at 12:06
  • This solution still works perfectly, but there is a lot of other. – Jackkobec Jun 16 '20 at 22:17
  • Use "react-router-dom": "4+" for this solution. We use "react-router-dom": "4.3.1" – Jackkobec Jun 21 '20 at 12:41
  • I just tried that again, still won't work. I actually use `"react-router-dom": "5.2.0"`, even though the `url` changes the browser won't surf to the required page. I think I might open a different question, this seems weird. – YanivGK Jun 21 '20 at 16:24
  • 1
    This solution doesn't work with Router v5. Use "react-router-dom": "4+" for this solution. We use "react-router-dom": "4.3.1 – Jackkobec Jun 21 '20 at 21:29
4

To build on Eni Arinde previous answer's (I don't have the reputation to comment), here is how to use the store.dispatch method after an async action :

export function myAction(data) {
    return (dispatch) => {
        dispatch({
            type: ACTION_TYPE,
            data,
        }).then((response) => {
            dispatch(push('/my_url'));
        });
    };
}

The trick is to do it in the action files and not in the reducers, since reducers should not have side effects.

Mathieu Nohet
  • 584
  • 6
  • 16
  • 1
    Even that this solution works, actions shouldn't be aware of routing at all IMO. You should be able to dispatch an action without any routing. – wintercounter Sep 26 '17 at 13:40
  • Is this a right way to approach the problem? Should we pass history object from component to the action creator for routing? – Jitin Maherchandani Jan 30 '18 at 05:41
  • 1
    An alternative - https://github.com/johanneslumpe/redux-history-transitions – Nabarun Mar 26 '18 at 22:57
  • 2
    How could you chain dispatch calls? dispatch().then(()=>dispatch) ? It doesn't seem to work. 'then is not a function' – Nour Jan 18 '19 at 18:13
3

You can use {withRouter} from 'react-router-dom'

Example below demonstrates a dispatch to push

export const registerUser = (userData, history) => {
  return dispatch => {
    axios
    .post('/api/users/register', userData)
    .then(response => history.push('/login'))
    .catch(err => dispatch(getErrors(err.response.data)));
  }
}

The history arguments is assigned to in the component as the a second parameter to the action creator (in this case 'registerUser')

milesr
  • 31
  • 1
  • 5
2

We can use "connected-react-router".

    import axios from "axios";
    import { push } from "connected-react-router";
    
    export myFunction = () => {
      return async (dispatch) => {
        try {
          dispatch({ type: "GET_DATA_REQUEST" });
          const { data } = await axios.get("URL");
          dispatch({
            type: "GET_DATA_SUCCESS",
            payload: data
          });
        } catch (error) {
          dispatch({
            type: "GET_DATA_FAIL",
            payload: error,
          });
          dispatch(push("/notfound"));
        }
      };
    };

Attention-- Please go to https://github.com/supasate/connected-react-router read the docs and set up the connected-react-router first, and then use "push" from connected-react-router.

bguiz
  • 22,661
  • 40
  • 140
  • 226
Alex
  • 21
  • 2
0
signup = e => {
  e.preventDefault();
  const { username, fullname, email, password } = e.target.elements,
    { dispatch, history } = this.props,
    payload = {
      username: username.value,
      //...<payload> details here
    };
  dispatch(userSignup(payload, history));
  // then in the actions use history.push('/<route>') after actions or promises resolved.
};

render() {
  return (
    <SignupForm onSubmit={this.signup} />
    //... more <jsx/>
  )
}
ravibagul91
  • 16,494
  • 4
  • 24
  • 45
0

Here is the working copy of routing app

    import {history, config} from '../../utils'
        import React, { Component } from 'react'
        import { Provider } from 'react-redux'
        import { createStore, applyMiddleware } from 'redux'
        import Login from './components/Login/Login';
        import Home from './components/Home/Home';
        import reducers from './reducers'
        import thunk from 'redux-thunk'

        import {Router, Route} from 'react-router-dom'

        import { history } from './utils';

        const store = createStore(reducers, applyMiddleware(thunk))



        export default class App extends Component {
          constructor(props) {
            super(props);

            history.listen((location, action) => {
              // clear alert on location change
              //dispatch(alertActions.clear());
            });
          }
          render() {
            return (
              <Provider store={store}>
                <Router history={history}>
                  <div>
                    <Route exact path="/" component={Home} />
                    <Route path="/login" component={Login} />
                  </div>
                </Router>
              </Provider>
            );
          }
        }

export const config = {
    apiUrl: 'http://localhost:61439/api'
};
import { createBrowserHistory } from 'history';

    export const history = createBrowserHistory();
//index.js
export * from './config';
export * from './history';
export * from './Base64';
export * from './authHeader';

import { SHOW_LOADER, AUTH_LOGIN, AUTH_FAIL, ERROR, AuthConstants } from './action_types'

import Base64 from "../utils/Base64";

import axios from 'axios';
import {history, config, authHeader} from '../utils'
import axiosWithSecurityTokens from '../utils/setAuthToken'


export function SingIn(username, password){


    return async (dispatch) => {
      if(username == "gmail"){
        onSuccess({username:"Gmail"}, dispatch);
      }else{
      dispatch({type:SHOW_LOADER, payload:true})
        let auth = {
            headers: {
              Authorization: 'Bearer ' + Base64.btoa(username + ":" + password)
            }
          }
        const result = await axios.post(config.apiUrl + "/Auth/Authenticate", {}, auth);
        localStorage.setItem('user', result.data)
        onSuccess(result.data, dispatch);
    }
  }

}

export function GetUsers(){
  return async (dispatch) => {
var access_token = localStorage.getItem('userToken');
    axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}` 

    var auth = {
      headers: authHeader()
    }
    debugger
      const result = await axios.get(config.apiUrl + "/Values", auth);
      onSuccess(result, dispatch);
      dispatch({type:AuthConstants.GETALL_REQUEST, payload:result.data})
  }
}



const onSuccess = (data, dispatch) => {

  const {username} = data;
  //console.log(response);
  if(username){
    dispatch({type:AuthConstants.LOGIN_SUCCESS, payload: {Username:username }});
    history.push('/');
    // Actions.DashboardPage();
  }else{
    dispatch({ type: AUTH_FAIL, payload: "Kullanici bilgileri bulunamadi" });
  }
  dispatch({ type: SHOW_LOADER, payload: false });
}
const onError = (err, dispatch) => {
  dispatch({ type: ERROR, payload: err.response.data });
  dispatch({ type: SHOW_LOADER, payload: false });
}

export const SingInWithGmail = () => {
  return { type :AuthConstants.LOGIN_SUCCESS}
}

export const SignOutGmail = () => {
  return { type :AuthConstants.LOGOUT}
}
0

An updated answer using hooks; for router v5 users.

Working on react-router-dom:5.1.2.

No installation of external package is required.

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

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

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

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

You can use the history as you're previously used to.

More more details and APIs - read the manual

willem
  • 22,847
  • 19
  • 68
  • 110
YanivGK
  • 541
  • 8
  • 15