Transition to another route on successful async redux action

30,347

Solution 1

If you don't want to use a more complete solution like Redux Router, you can use Redux History Transitions which lets you write code like this:

export function login() {
  return {
    type: LOGGED_IN,
    payload: {
      userId: 123
    }
    meta: {
      transition: (state, action) => ({
        path: `/logged-in/${action.payload.userId}`,
        query: {
          some: 'queryParam'
        },
        state: {
          some: 'state'
        }
      })
    }
  };
}

This is similar to what you suggested but a tiny bit more sophisticated. It still uses the same history library under the hood so it's compatible with React Router.

Solution 2

I ended up creating a super simple middleware that roughtly looks like that:

import history from "../routes/history";

export default store => next => action => {

    if ( ! action.redirect ) return next(action);

    history.replaceState(null, action.redirect);
}

So from there you just need to make sure that your successful actions have a redirect property. Also note, this middleware does not trigger next(). This is on purpose as a route transition should be the end of the action chain.

Solution 3

For those that are using a middleware API layer to abstract their usage of something like isomorphic-fetch, and also happen to be using redux-thunk, you can simply chain off your dispatch Promise inside of your actions, like so:

import { push } from 'react-router-redux';
const USER_ID = // imported from JWT;

function fetchUser(primaryKey, opts) {
    // this prepares object for the API middleware
}

// this can be called from your container
export function updateUser(payload, redirectUrl) {
    var opts = {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    };
    return (dispatch) => {
        return dispatch(fetchUser(USER_ID, opts))
            .then((action) => {
                if (action.type === ActionTypes.USER_SUCCESS) {
                    dispatch(push(redirectUrl));
                }
            });
    };
}

This reduces the need for adding libraries into your code as suggested here, and also nicely co-locates your actions with their redirects as done in redux-history-transitions.

Here is what my store looks like:

import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
import api from '../middleware/api';
import { routerMiddleware } from 'react-router-redux';

export default function configureStore(initialState, browserHistory) {
    const store = createStore(
        rootReducer,
        initialState,
        applyMiddleware(thunk, api, routerMiddleware(browserHistory))
    );

    return store;
}
Share:
30,347
Clarkie
Author by

Clarkie

Software engineer, cyclist & surfer. Co-founder of Evidenced Make better hiring decisions with Evidenced. Give your teams the tools to deliver consistent, fair and measurable interviews, driven by data. Organiser of SushiJS in London. Member of London Node User Group (LNUG) organisation committee.

Updated on October 11, 2020

Comments

  • Clarkie
    Clarkie over 3 years

    I have a pretty simple set of react components:

    • container that hooks into redux and handles the actions, store subscriptions, etc
    • list which displays a list of my items
    • new which is a form to add a new item to the list

    I have some react-router routes as follows:

    <Route name='products' path='products' handler={ProductsContainer}>
      <Route name='productNew' path='new' handler={ProductNew} />
      <DefaultRoute handler={ProductsList} />
    </Route>
    

    so that either the list or the form are shown but not both.

    What I'd like to do is to have the application re-route back to the list once a new item has been successfully added.

    My solution so far is to have a .then() after the async dispatch:

    dispatch(actions.addProduct(product)
      .then(this.transitionTo('products'))
    )
    

    Is this the correct way to do this or should I fire another action somehow to trigger the route change?