Skip to content
Tyler Shaddix edited this page Mar 29, 2016 · 18 revisions

Async Actions

There is often the need to have complex or asynchronous action handling in apps. This package splits action generation and action handling into two separate pieces of the application (UI Components and the background page), making async actions a little tricky. The alias middleware aims to solve this complexity.

alias

alias is a simple middleware which can map actions to new actions. This is useful for asynchronous actions, where the async action handling needs to take place in the background page, but it kicked off by an action from a UI Component.

For example, let's say you want to get the current session in your UI components when your component mounts. Let's start by firing off a simple action GET_SESSION.

// popover/App.jsx

import React, {Component} from 'react';
import {connect} from 'react-redux';

// the mock action
const getSession = () => {
  const data = {
    type: GET_SESSION,
    payload: {}
  };

  return data;
};

class App extends Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    this.props.dispatch(getSession());
  }

  render() {
    return (
      <div>
        {this.props.session && this.props.users[this.props.session.userId].name}
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    session: state.session,
    users: state.users
  };
};

export default connect(mapStateToProps)(App);

All of our session logic lives in our background page (such as fetching from local storage or making a web request). If we leave this as is, our Redux store would get an action of {type: GET_SESSION}, which is not very useful to our reducers considering we don't have any session data yet.

Using an alias, we can intercept the original GET_SESSION and replace it with a new action that we generate on the background page. We start by writing the intercept function, which will receive the original action and produce a new action, and then export it under the name of the action we will intercept:

// aliases.js

const getSession = (orginalAction) => {
  // return a thunk/promise/etc
};

export default {
  'GET_SESSION': getSession // the action to proxy and the new action to call
};

Then you just include it in your middleware for Redux:

import {alias} from 'react-chrome-redux';

import aliases from '../aliases';

const middleware = [
  alias(aliases),
  // whatever middleware you want (like redux-thunk)
];

// createStoreWithMiddleware... you know the drill

Now, when your Redux store sees an action of GET_SESSION, it will run our getSession() alias in our aliases file and return the result as the new action.

The nice thing about doing this through a middleware is that you can run it with other packages such as react-thunk. Your alias can return a function to run instead of an action object, and everything will proceed as normal.

Action Responses

dispatchResponder

It's a common practice to have Redux dispatches return promises with something like redux-thunk. This package provides a way to simulate this behavior by providing a dispatchResponder in the store wrapper for the background page.

When an action is dispatched from a UI Component, it will return a promise that will resolve/reject with a response from the background page:

// the mock action
const getSession = () => {
  const data = {
    type: ACTION_GET_SESSION,
    payload: {}
  };

  return data;
};

class App extends Component {
  constructor(props) {}

  componentDidMount() {
    // promise returned from `dispatch()`
    this.props.dispatch(getSession())
      .then((data) => {
        // the response data
      })
      .catch((err) => {
        // something broke in the background store
      });
  }

  render() {}
}

As you can quickly tell, this is really nice for making UI updates for error/success messages or pending states. By default, this is done by calling a simple Promise.resolve() on the dispatch result in the background and responding with an object of {error, value} based on rejection or resolution.

What if you don't return promises from your dispatches? Or what if those promises are hidden away in somewhere in the payload? You can pass in a dispatchResponder to your store wrapper when calling wrapStore to take care of that:

// redux-promise-middleware returns promises under `promise` field in the payload

/**
 * Respond to action based on `redux-promise-middleware` result
 * @param  {object} dispatchResult The resulting object from `store.dispatch()`
 * @param  {func}   send           func to be called when sending response. Should be in form {value, error}
 */
const reduxPromiseResponder = (dispatchResult, send) => {
  Promise
    .resolve(dispatchResult.payload.promise) // pull out the promise
    .then((res) => {
      // if success then respond with value
      send({
        error: null,
        value: res
      });
    })
    .catch((err) => {
      // if error then respond with error
      send({
        error: err,
        value: null
      });
    });
};

// ...

// Add responder to store
wrapStore(store, {
  portName: 'MY_APP',
  dispatchResponder: reduxPromiseResponder
});
Clone this wiki locally