FSA-compliant promise middleware for Redux with simple behaviour with minimal boilerplate declarations

Overview

redux-simple-promise

build status npm version

FSA-compliant promise middleware for Redux with simple behaviour with minimal boilerplate declarations.

npm install --save redux-simple-promise

Usage

First, import the middleware creator and include it in applyMiddleware when creating the Redux store. You need to call it as a function (See later why on configuration section below):

import promiseMiddleware from 'redux-simple-promise';

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware()
)(createStore);

To use the middleware, dispatch a promise property and optional additional properties within the payload of the action and specify the action type string as you normally do.

The pending action is dispatched immediately, with type the same as the original dispatching action with all original payload properties apart from the promise as the payload object (those are useful for optimistic updates). The resolve action is dispatched only if the promise is resolved, e.g., if it was successful; and the rejected action is dispatched only if the promise is rejected, e.g., if an error occurred.

Both fullfilled actions (resolved and rejected) will be dispatched with the result of the promise as the payload object and all other remaining properties will be dispatched inside the meta property. More specifically, in the case of a rejected promise, an error is returned in the payload property. Also those fullfiled actions will have the original type added by a suffix (default is _RESOLVED for resolved and _REJECTED for rejected).

Example:

The below action creator, when triggered dispatch(loadUser('alanrubin'))

export function loadUser(username) {
  return {
    type: 'LOAD_USER',
    payload: {
      promise: loadUserServiceAndReturnPromise(username)
      username
    }
  };
}

will dispatch immediatelly

{
	type: 'LOAD_USER',
	payload: {
		username: 'alanrubin'
	}
}

Assuming promise resolves with { id: '1', name: 'Alan Rubin' }, then it will dispatch

{
	type: 'LOAD_USER_RESOLVED',
	payload: { id: '1', name: 'Alan Rubin' },
	meta: {
		username: 'alanrubin'
	}
}

Assuming promise rejects with Error object, then it will dispatch

{
	type: 'LOAD_USER_REJECTED',
	payload: Error,
	meta: {
		username: 'alanrubin'
	}
}

The middleware also returns the original promise, so you can listen to it and act accordingly from your component if needed (for example redirecting to a new route).

The middleware doesn't include the original promise in the 3 processed actions as it is not useful in the reducers - it is a bad practice to store promises in the state as the state should be serializable.

Usage in reducers

Another nice feature is that resolve and reject functions can be imported from the package in order to provide nice semantic switch conditions when writing reducers for those actions. Assuming the example above, in your reducer:

import { resolve, reject } from 'redux-simple-promise';

function users(state = {}, action) {
  switch (action.type) {
  case LOAD_USER:
    return Object.assign({}, state, {
      action.payload.username: { isLoading: true }
    });
  case resolve(LOAD_USER):
    return Object.assign({}, state, {
      action.meta.username: action.payload
    });
  case reject(LOAD_USER):
  	return Object.assign({}, state, {
      action.meta.username: { error: action.payload }
    });
  default:
    return state;
  }
}

Similarly you can use unresolve and unreject functions available to reverse the action string. This is useful specially if action.type as a key in the state tree, so we could obtain the same string when ACTION, resolve(ACTION) and reject(ACTION) is called in the reducer.

import { resolve, reject, unresolve, unreject } from 'redux-simple-promise';

function users(state = {}, action) {
  switch (action.type) {
  case LOAD_USER:
    return Object.assign({}, state, {
      [LOAD_USER]: { isLoading: true }
    });
  case resolve(LOAD_USER):
    return Object.assign({}, state, {
      [unresolve(LOAD_USER)]: action.payload // From LOAD_USER_RESOLVED to LOAD_USER
    });
  case reject(LOAD_USER):
  	return Object.assign({}, state, {
      [unreject(LOAD_USER)]: { error: action.payload } // From LOAD_USER_REJECTED to LOAD_USER
    });
  default:
    return state;
  }
}

Configuration

You can configure the string being added to the action type when resolved or rejected by declaring it when initialiazing the middleware, so considering the example above, if you do

import promiseMiddleware from 'redux-simple-promise';

composeStoreWithMiddleware = applyMiddleware(
  promiseMiddleware('_MY_RESOLVED', '_MY_REJECTED')
)(createStore);

then resolved/rejected promised will trigger actions as 'LOAD_USER_MY_RESOLVED' and 'LOAD_USER_MY_REJECTED' instead of the default ones 'LOAD_USER_RESOLVED' and 'LOAD_USER_REJECTED'.

Inspiration

I have tried to mix the best behaviour from both redux-promise and redux-promise-middleware projects, avoiding as much as possible additional boilerplate declarations (such as declaring 3 times the action type or passing the arguments of the first dispatch in data or meta) and the most consistent behavior (at least in my opinion...).

Thanks to both projects for inspiration, specially to redux-promise for the project setup and test inspiration.


Licensed MIT. Copyright 2015 Alan Rubin.

Comments
  • Add unresolve / unreject methods

    Add unresolve / unreject methods

    I found those methods would be useful in cases where we use the action.type as a key in the state tree, so we could obtain the same string when ACTION and resolve(ACTION) or reject(ACTION) is called in the reducer.

    opened by alanrubin 0
  • Release new version?

    Release new version?

    Hey there - I was just wondering if there's any plans to release a new version to npm? The current one was released 3 years ago and I've been having problems with the latest version of babel:

    Using removed Babel 5 option
    

    Also, the flux-standard-action dependency is already on a major 2.0.2, while this tool uses the version 0.0.6.

    Would be great if this lib could get a new release with updated dependencies!

    opened by lucasmotta 0
  • Do not clobber action meta

    Do not clobber action meta

    Previously, if an action had both meta data and payload, the metadata would be clobbered with the payload once the promise had either been rejected or resolved.

    Now, the original payload is added to the metadata under the key payload preserving any meta already present; unless there was meta under the key payload which seems unlikely.

    This is a breaking API change.

    opened by benarmston 3
  • Throw error when a promise is rejected

    Throw error when a promise is rejected

    This change makes it possible for client code to distinguish between rejected and failed promises when attaching then, or catch handlers. With the previous behaviour it was impossible to make this distinction (at least using the promise API).

    If the old behaviour is desired, a client can add a catch handler which returns the error before dispatching:

    dispatch(myPromise.catch((err) => err))
    
    opened by benarmston 8
  • Throwing Vs Returning the Error

    Throwing Vs Returning the Error

    Following from our discussion - I was wondering if it's a good idea to throw the error when the promise rejects instead of returning it. https://www.facebook.com/groups/reactjsil/permalink/1619829844950813/

    opened by benjamingr 1
Owner
Alan Rubin
Alan Rubin
Analytics middleware for Redux

redux-analytics Analytics middleware for Redux. $ npm install --save redux-analytics Want to customise your metadata further? Check out redux-tap. Usa

Mark Dalgleish 490 Aug 5, 2022
A mock store for testing Redux async action creators and middleware.

redux-mock-store A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which

Redux 2.5k Jan 1, 2023
Redux Tutorial - share my experience regarding redux, react-redux and redux-toolkit

Redux Tutorial 1. Introduction to Redux 1.1 What is Redux & why Redux? A small JS Library for managing medium/large amount of states globally in your

Anisul Islam 36 Dec 29, 2022
Skeleton React App configured with Redux store along with redux-thunk, redux persist and form validation using formik and yup

Getting Started with React-Redux App Some Configrations Needed You guys need to modify the baseUrl (path to your server) in the server.js file that is

Usama Sarfraz 11 Jul 10, 2022
A simple app for study react with redux, redux saga and typescript.

React com Redux, Redux-Saga e TypeScript. ?? Uma aplicação simple para entender o funcionamento do Redux e a melhor maneira de utiliza-lo junto com o

João Marcos Belanga 1 May 24, 2022
An example real-world Redux CRUD application with no boilerplate

Zero Boilerplate Redux A common criticism of Redux is that it requires writing a lot of boilerplate. This is an application that is meant to show a so

James 18 Nov 23, 2022
A Higher Order Component using react-redux to keep form state in a Redux store

redux-form You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of Redux

Redux Form 12.6k Jan 3, 2023
redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state.

redux-immutable redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state. When Redux creat

Gajus Kuizinas 1.9k Dec 30, 2022
A chart monitor for Redux DevTools https://www.npmjs.com/package/redux-devtools-chart-monitor

Redux DevTools Chart Monitor This package was merged into redux-devtools monorepo. Please refer to that repository for the latest updates, issues and

Redux 293 Nov 13, 2022
Redux - Create forms using Redux And React

Exercício de fixação Vamos criar formulários utilizando Redux! \o/ Antes de inic

Márcio Júnior 2 Jul 21, 2022
A lightweight state management library for react inspired by redux and react-redux

A lightweight state management library for react inspired by redux and react-redux

null 2 Sep 9, 2022
Ruthlessly simple bindings to keep react-router and redux in sync

Project Deprecated This project is no longer maintained. For your Redux <-> Router syncing needs with React Router 4+, please see one of these librari

React Community 7.9k Dec 30, 2022
A simple, lightweight library for managing navigation history in React and Redux

?? Beta version ⚛ Redux history made easy! A simple, lightweight library for managing navigation history in React and Redux. Used in production by Uti

Isis☕Caffe 6 Dec 22, 2022
Official React bindings for Redux

React Redux Official React bindings for Redux. Performant and flexible. Installation Using Create React App The recommended way to start new apps with

Redux 22.5k Jan 1, 2023
DevTools for Redux with hot reloading, action replay, and customizable UI

Redux DevTools Developer Tools to power-up Redux development workflow or any other architecture which handles the state change (see integrations). It

Redux 13.3k Jan 2, 2023
The official, opinionated, batteries-included toolset for efficient Redux development

Redux Toolkit The official, opinionated, batteries-included toolset for efficient Redux development (Formerly known as "Redux Starter Kit") Installati

Redux 8.9k Dec 31, 2022
Logger for Redux

Logger for Redux Now maintained by LogRocket! LogRocket is a production Redux logging tool that lets you replay problems as if they happened in your o

null 5.7k Jan 1, 2023
Selector library for Redux

Reselect Simple “selector” library for Redux (and others) inspired by getters in NuclearJS, subscriptions in re-frame and this proposal from speedskat

Redux 18.8k Dec 27, 2022
An alternative side effect model for Redux apps

redux-saga redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like acce

Redux-Saga 22.4k Jan 5, 2023