redux-better-promise
Simple and powerful redux middleware that supports async side-effects (and much more)
Redux middleware that will allow you to:
- Create actions with side-effects that will dispatch different actions on different side-effect results
- Create actions with both async and sync side-effects
- Add hooks (callbacks) to you async actions so that you can treat dispatched actions like events and just react to them (e.g. show notification without checking if state changed)
FAQ
Why this exists?
There are some good middlewares that help manage side-effects in redux actions but they generally allow you to create actions creators that are vastly different then standard ones. I decided to make middleware with which you will be able to make action creators that will look consistent with the rest of the application and have all the other middlewares' power combined!
Are there good alternatives?
Yes and you should definitely check them out:
I found a bug! What should I do?
There are at least 3 options:
- Add an issue, write test(s) for bug you found, write fix that will make your test(s) pass, submit pull request
- Add an issue, write test(s) for bug you found, submit pull request with you test(s)
- Add an issue
All contributions are appreciated!
Getting started
Install library
npm i redux-better-promise --save
Apply middleware to redux store
;; const store = ;
Usage
Simple async action creator
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' Promise ;}
Dispatching action above will lead to dispatching following actions:
// before calling `promise` function type: 'ACTION_STARTED' // after promise is resolved type: 'ACTION_SUCCEEDED' result: some: 'data'
If promise returned by promise
function is rejected with { some: 'error' }
second action will look like this
// after promise is rejected type: 'ACTION_ERROR' error: some: 'error'
Simple sync action with function
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' some: 'data' ;}
Dispatching action above will result in dispatching following actions:
// before `function` function is called type: 'ACTION_STARTED' // after `function` returns result type: 'ACTION_SUCCEEDED' result: some: 'data'
If function
throws a { some: 'error' }
second action will look like this
// after throwing an error type: 'ACTION_ERROR' error: some: 'error'
Adding hooks to actions
Hooks are just functions triggered when particular action is going to be dispatched. They cannot modify dispatching process in any way but can be used to trigger custom actions on some actions (like showing notification when async actions fails)
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' Promise hooks: ;}
When dispatching action above notification('I started')
will be triggered immediately and notification('I finished successfully')
will be triggered after promise resolves.
notification('I failed')
will not be triggered unless promise fails
Adding additional data to actions
Start, success and error actions can contain additional data e.g. payload
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' Promise payload: 'some payload' whatever: 'some other data' ;}
Here resulting actions would look like this:
// before calling `promise` function type: 'ACTION_STARTED' payload: 'some payload' whatever: 'some other data' // after promise is resolved type: 'ACTION_SUCCEEDED' result: some: 'data' payload: 'some payload' whatever: 'some other data'
Passing lists as objects
types
and hooks
fields can be objects
{ return types: start: 'ACTION_STARTED' success: 'ACTION_SUCCEEDED' error: 'ACTION_ERROR' Promise hooks: ;}
promise
or function
Adding custom parameters that are going to passed to By default promise
and function
functions will get { getState, dispatch }
object as the first parameter. You can add any additional fields to that object:
const store = ;
Now actions can look like this:
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' Promise ;}
or
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' some: 'data' ;}
Omitting some action types or hooks
You don't have to provide all action types or hooks
{ return types: null null 'ACTION_ERROR' Promise hooks: null null ;}
Now the only action dispatched will be 'ACTION_ERROR' (of course only if promise returned by promise
function will be rejected) and only before calling promise
function notification('I started')
hook is going to be called
Providing only one action type
Instead of providing action with types
field you can pass only one action type in type
field:
{ return type: 'ACTION_SUCCESS' Promise ;}
Now 'ACTION_SUCCESS' action would be triggered only if promise
action resolves. So in this case no action is going to be dispatched.
Registering global hooks
Instead of adding hooks to each action you can add global ones in hooks
field of a second argument of middlewareCreator:
; const store = ;
You can also replace actionType with actionTypeExclude which will just inverts the behaviour of that field or even use both fields (the exclude
field will overwrite the include
filed):
; const store = ;
There are different generic types in actionTypes named export object:
; actionTypesstart;actionTypessuccess;actionTypeserror;actionTypesfinish; // success and error
Changing default field names
Most of the time it's better not to have fields named function
in you code ;)
You can change all default field names by providing any number of configFields as a second argument to middlewareCreator
const store = ;
Now actions can look like this:
{ return types: 'ACTION_STARTED' 'ACTION_SUCCEEDED' 'ACTION_ERROR' some: 'data' ;}
Your config object will be deeply merged with default options:
promiseFieldName: 'promise' functionFieldName: 'function' typesFieldName: 'types' hooksFieldName: 'hooks' typesNames: // used when types field is object start: 'start' success: 'success' error: 'error' hooksNames: // used when hooks field is object start: 'start' success: 'success' error: 'error'
Note that you cannot change type
field name!
Good practises
no-dispatch actions
Theoretically you could create actions that does not influence redux store. For example:
{ return Promise ;}
When dispatching action above no action will actually be dispatched on redux store. As using redux in this way doesn't make sense, actions that has neither types
nor type
field will throw an error when dispatched.
Note that you still can make a workaround:
{ return types: null null null Promise ;}
Although in this version such action will not throw an error it is generally bad idea to create it. Even with hooks, it's much better (and easier to reason about) when calling promise directly.
conditional dispatchers
As with redux-thunk
or redux-saga
you can create action that will conditionally dispatch other actions:
{ return makeItSo ? : null ;}
Most of the time it's not a good idea. It's probably better to just conditionally dispatch someOtherAction than to create wrapper action to do that for you.
Roadmap
- Errors in success hooks should not be caught together with errors in
function
- Refactor tests (merge some tests and split others to make them more readable)
What's not going to be done
- You will not be able to dynamically add hooks (like listeners) to actions (e.g. with middleware.addHook()) except in action creators. The power of redux is in the ability to quickly find the reason why something happened and adding dynamic listeners will make the code hard to understand.