Creating promise actions in redux

If you're reading this you probably already know something about Redux, and how it works with React. Just in case you don't here's a quick recap of the steps. This can seem quite complex at first, but the pattern is simple and always the same - which is one of the advantages of using Redux.

In your React component, you connect to the part of the Redux store you are interested in using the @connect decorator.

This injects a reference to data from that store into the props of the component, along with a reference to the store dispatcher.

When something happens and you need to update the state in the store, you need to

create an action consisting of a type, and optionally a payload. This is normally done using an action creator function that returns a plain object.

dispatch that action creator.

Next your reducer, whose job is to put things in the store, is called by Redux. That reducer will make a new copy of the state in the store, and add whatever is in the payload.

Example

When you create an action to be dispatched to Redux, that action object needs to be a plain object, and normally looks like this.

{ type:'SOMETHING_WONDERFUL', payload:magnificentPayload }

An action might look like this.

const acSomethingWonderful = (payload) => {

return {type: "SOMETHING_WONDERFUL" , payload:payload};

}

In your React component, you would dispatch that action creator.

this.props.dispatch (acSomethingWonderful());

Asynchronicity

So far so good, but when you have to deal with asynchronous actions (such as fetching data from an API), things can get complicated, and you may be tempted to start messing with the pattern, but there's no need.

In this example, the user has clicked the button to signout of my application, which has been authenticated using Firebase. However I can't dispatch an action saying he has signed out, because that hasn't actually happened yet. The state we're currently in is that we've asked Firebase to sign out, but it's not yet completed. This means that any React components which are being rendered based on someone being signed in now need to be updated, but not yet - not until the signout is completed, so this simple action creator will not work.

This is wrong...

const acSignout = (payload) => {

return {type: "AUTH_SIGNOUT" , payload:payload};

}

Dispatching multiple actions

To make this right, multiple actions need to be dispatched. One to say that the signout process has started, and another when it has finished (or failed). Here's an improved version, wrapped in a function that will end up creating multiple actions. These snippets are taken from the Ephemeral Exchange management console.

export function acSignout() {

return acPromise (

cs.actions.AUTH_SIGNOUT,

getCurrentUid(),

() => firebase.signOut

);

}

and in the component that detected the signout request

this.props.dispatch (acSignout());

The function to be executed (firebase.signOut), is passed to the wrapper function below, which turns it into a promise (if it's not already one).

While firebase.signOut is being executed it returns an action creator of type AUTH_SIGNOUT_PENDING.

You have the opportunity of catching that PENDING action in your reducer which will also receive the pendingPayload argument to put in the store. During this time all your subscribed components will get to know that there is a signout on operation, and the payload will have signalled who is signing out.

When the firebase.signOut is completed, it dispatches another action of type AUTH_SIGNOUT_FULFILLED

The reducer will action this new status so the rest of the App can get to know that the signout has happened, and that all the dependent components can be re-rendered to reflect this new state.

Note that the AUTH_SIGNOUT is never dispatched. Only the _PENDING and _FULFILLED (or _REJECTED) actions need to be handled in the reducer

Note that the function contains a reference to the store dispatcher to avoid passing it through each time. This is because I will turn this into middleware at some point and won't need the dispatcher reference then.

/**

* actionType_PENDING is returned from this

* and later when the function is completed, actionType_FULFILLED or _REJECTED are dispatched

* @param {string} actionType the base action type

* @param {*} pendingPayload the payload to dispatch with the _PENDING action

* @param {func} function the function to execute that should return a promise

* @return {object} the action to be dispatched

*/

export function acPromise (actionType, pendingPayload, func) {

// later on I'll make this middleware so i dont need to pass the dispatcher

// now we execute the thing, but dispatch a fullfilled/rejected when done

theAction()

.then (function (result) {

// the result of the original function

dispatch({

type:actionType+"_FULFILLED",

payload:result

});

})

.catch (function (err) {

dispatch({

type:actionType+"_REJECTED",

payload:err

});

});

// what we return is the pending action

return {

type:actionType+"_PENDING",

payload:pendingPayload

};

}

Here's the Redux logger view of what happened following the dispatch.

Redux-promise middleware

There are a couple of middlewares available such as redux-promise that can help with this, but I decided to roll my own, mainly because I wanted to be able to pass a payload while in the PENDING state too, and couldn't figure out how to do that with those other solutions. This creates the same action variants (PENDING,FULFILLED,REJECTED) as redux-promise.