63

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?

Clarkie
  • 7,490
  • 9
  • 39
  • 53
  • 1
    Have you take a look at https://github.com/acdlite/redux-react-router ? It stores router store in redux so you are able to trigger route changes as redux actions. – Dmitry Zaets Sep 17 '15 at 09:07
  • I've had a look at `redux-react-router` and this will require a large refactor so may have to wait. If I come up with a solution in the meantime I'll post an answer – Clarkie Sep 17 '15 at 10:42
  • @Dmitry the question then is where you trigger from once props (e.g. a isAuthenticated flag) are updated. componentWillReceiveProps? – backdesk Jul 05 '16 at 14:39

5 Answers5

29

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.

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
26

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.

silkAdmin
  • 4,640
  • 10
  • 52
  • 83
20

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;
}
Community
  • 1
  • 1
Cameron
  • 1,524
  • 11
  • 21
  • does that really works... cuz its aint working for me – gamer Apr 28 '16 at 07:25
  • 2
    Not working in the sense url get redirected but my content in the page does not change.. It displays the same page even when url is changed.. – gamer Apr 29 '16 at 04:15
  • are you using a router? which router and what add-ons? This code is written with React-Router and React-Router-Redux in mind – Cameron Apr 29 '16 at 18:21
  • This worked for me, but you must have the `routerMiddleware` in included in your store https://github.com/reactjs/react-router-redux#what-if-i-want-to-issue-navigation-events-via-redux-actions – HussienK Aug 04 '16 at 20:27
  • 2
    For those like me wondering where function `push` is coming from: `import { push } from 'react-router-redux'` – Rahul Desai Apr 07 '17 at 16:52
0

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.

Manjeet Singh
  • 4,382
  • 4
  • 26
  • 39
0

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).
Brad W
  • 190
  • 3
  • 5