Clarkie
Clarkie

Reputation: 7550

Transition to another route on successful async redux action

I have a pretty simple set of react components:

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?

Upvotes: 63

Views: 31206

Answers (5)

Brad W
Brad W

Reputation: 190

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).

Upvotes: 0

silkAdmin
silkAdmin

Reputation: 4800

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.

Upvotes: 26

Manjeet Singh
Manjeet Singh

Reputation: 4562

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.

Upvotes: 0

Cameron
Cameron

Reputation: 1524

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

Upvotes: 20

Dan Abramov
Dan Abramov

Reputation: 268215

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.

Upvotes: 29

Related Questions