Transition to another route on successful async redux action

JavascriptReactjsReact RouterRedux

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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;
}

Solution 4 - Javascript

I know I am little late in the party as react-navigation is already included in the react-native documentation, but still it can be useful for the user who have used/using Navigator api in their apps. what I tried is little hackish, I am saving navigator instance in object as soon as renderScene happens-

renderScene(route, navigator) {
      const Component = Routes[route.Name]
      api.updateNavigator(navigator); //will allow us to access navigator anywhere within the app
      return <Component route={route} navigator={navigator} data={route.data}/>
  
}

my api file is something lke this

'use strict';

//this file includes all my global functions
import React, {Component} from 'react';
import {Linking, Alert, NetInfo, Platform} from 'react-native';
var api = {
    navigator,
    isAndroid(){
        return (Platform.OS === 'android');
    },
    updateNavigator(_navigator){
      if(_navigator)
          this.navigator = _navigator;
    },
}

module.exports = api;

now in your actions you can simply call

> api.navigator.push({Name:'routeName', > data:WHATEVER_YOU_WANTED_TO_PASS)

you just need to import your api from the module.

Solution 5 - Javascript

If you're using react-redux and react-router, then I think this link provides a great solution.

Here's the steps I used:

  • Pass in a react-router history prop to your component, either by rendering your component inside a react-router <Route/> component or by creating a Higher Order Component using withRouter.
  • Next, create the route you want to redirect to (I called mine to).
  • Third, call your redux action with both history and to.
  • Finally, when you want to redirect (e.g., when your redux action resolves), call history.push(to).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionClarkieView Question on Stackoverflow
Solution 1 - JavascriptDan AbramovView Answer on Stackoverflow
Solution 2 - JavascriptsilkAdminView Answer on Stackoverflow
Solution 3 - JavascriptCameronView Answer on Stackoverflow
Solution 4 - JavascriptManjeet SinghView Answer on Stackoverflow
Solution 5 - JavascriptBrad WView Answer on Stackoverflow