Returning promises from Redux action creators

Redux action creators do the heavy lifting and dispatch actions along the way to update the Redux store. But how to best inform the caller when the action creator is done?

Action creators 101

The following action creator saves to the backend if a Todo item is done or open. Along the way, it dispatches Redux actions (using the Thunk middleware) about the saving status:

const update = (todoId, isDone) => (dispatch) => {
dispatch({
type: 'SET_SAVING',
saving: true
});
  // Function is expected to return a promise
callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});
});

// TBD: Handle errors
}

The reducer would then set the saving flag in the Redux store based on the dispatched SET_SAVING actions:

const savingReducer = (state = null, action) =>
action.type === 'SET_SAVING' ? action.saving : state;
const todosReducer = Redux.combineReducers({
saving: savingReducer
});

The calling element could use the saving flag e.g. to disabled the save button, thereby preventing the user from accidentally triggering multiple save operations in parallel.

Reacting on finished event

Things get a bit more tricky if we want to react to the event itself, e.g. to show a toast when the update was successfully completed. Although one could subscript to the SET_SAVING action, we found this cumbersome as it leaks internals of our Redux code (action-type names) to the UI element.

So what can we do?

Our preferred solution is to have the update function to directly return a promise that can used by the caller, e.g.:

const update = (todoId, isDone) => (dispatch) =>
new Promise(function(resolve, reject) {
dispatch({
type: 'SET_SAVING',
saving: true
});
    // Function is expected to return a promise
callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});

resolve(updatedTodo);
}).catch(error => {
// TBD: Handle errors for Redux

reject(error);
})
});

The caller can then simply use this promise, e.g.:

dispatch(update('todo-id', true)).then(updatedTodo => {
showToast('Todo item was successfully updated');
});

There is one more simplification: we can recycle the promise returned by the callUpdateApi function:

const update = (todoId, isDone) => (dispatch) => {
dispatch({
type: 'SET_SAVING',
saving: true
});
  // Function is expected to return a promise
return callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});

return updatedTodo;
});
};

Happy coding!

Want to learn more about Polymer? Have a look to all our Medium posts on Polymer.


Photo: Ronny Roeller