|
| 1 | +redux-await |
| 2 | +============= |
| 3 | + |
| 4 | +[![NPM version][npm-image]][npm-url] |
| 5 | +[![Build status][travis-image]][travis-url] |
| 6 | +[![Test coverage][coveralls-image]][coveralls-url] |
| 7 | +[![Downloads][downloads-image]][downloads-url] |
| 8 | + |
| 9 | +Manage async redux actions sanely |
| 10 | + |
| 11 | +## Install |
| 12 | + |
| 13 | +```js |
| 14 | +npm install --save redux-await |
| 15 | +``` |
| 16 | + |
| 17 | +## Usage |
| 18 | + |
| 19 | +This module exposes a middleware and higher order reducer to take care of async state in a redux app. You'll need to: |
| 20 | + |
| 21 | +1. Apply the middleware: |
| 22 | + |
| 23 | +```js |
| 24 | +import { middleware as awaitMiddleware } from 'redux-await'; |
| 25 | +let createStoreWithMiddleware = applyMiddleware( |
| 26 | + awaitMiddleware, |
| 27 | +)(createStore); |
| 28 | +``` |
| 29 | + |
| 30 | +2. Wrap your reducers |
| 31 | + |
| 32 | +```js |
| 33 | +const intialState = { |
| 34 | + users: [], |
| 35 | +} |
| 36 | + |
| 37 | +const reducer = (state = [], action = {}) => { |
| 38 | + if (action.type === GET_USERS) { |
| 39 | + return { ...state, users: action.payload.users }; |
| 40 | + } |
| 41 | + if (action.type === ADD_USER) { |
| 42 | + return { ...state, users: state.users.concat(action.payload.user) }; |
| 43 | + } |
| 44 | + return state; |
| 45 | +} |
| 46 | + |
| 47 | +// old code |
| 48 | +// export default reducer; |
| 49 | + |
| 50 | +// new code |
| 51 | +import { createReducer } from 'redux-await'; |
| 52 | +export default createReducer(reducer); |
| 53 | +``` |
| 54 | + |
| 55 | +Note, if you are using `combineReducers` then you need to wrap each reducer that you are combining independently and not the master reducer that `combineReducers` returns |
| 56 | + |
| 57 | +Now your action creators can contain promises: |
| 58 | + |
| 59 | +```js |
| 60 | +// old code |
| 61 | +//export const getUsers = users => ({ |
| 62 | +// type: ADD_USER, |
| 63 | +// payload: { |
| 64 | +// users: users, |
| 65 | +// }, |
| 66 | +//}); |
| 67 | +//export const addUser = user => ({ |
| 68 | +// type: ADD_USER, |
| 69 | +// payload: { |
| 70 | +// user: user, |
| 71 | +// }, |
| 72 | +//}); |
| 73 | + |
| 74 | +// new code |
| 75 | +import { AWAIT_MARKER } from 'redux-await'; |
| 76 | +export const getUsers = users => ({ |
| 77 | + type: ADD_USER, |
| 78 | + AWAIT_MARKER, |
| 79 | + payload: { |
| 80 | + users: api.getUsers(), // returns promise |
| 81 | + }, |
| 82 | +}); |
| 83 | +export const addUser = user => ({ |
| 84 | + type: ADD_USER, |
| 85 | + AWAIT_MARKER, |
| 86 | + payload: { |
| 87 | + user: api.generateUser(), // returns promise |
| 88 | + }, |
| 89 | +}); |
| 90 | +``` |
| 91 | + |
| 92 | +Now your containers can hardly need to change at all: |
| 93 | + |
| 94 | +```js |
| 95 | +import { getInfo } from 'redux-await' |
| 96 | + |
| 97 | +class Container extends Component { |
| 98 | + render() { |
| 99 | + const { users, user } = this.props; |
| 100 | + |
| 101 | + // old code |
| 102 | + //return <div> |
| 103 | + // <MyTable data={users} /> |
| 104 | + //</div>; |
| 105 | + |
| 106 | + // new code |
| 107 | + return <div> |
| 108 | + { getInfo(users).status === 'pending' && <div>Loading...</div> } |
| 109 | + { getInfo(users).status === 'success' && <MyTable data={users} /> } |
| 110 | + { getInfo(users).status === 'failure' && <div>Opps: {getInfo(users).error.message}</div> } |
| 111 | + { getInfo(user).status === 'pending' && <div>Saving new user</div> } |
| 112 | + { getInfo(user).status === 'failure' && <div>There was an error saving</div> } |
| 113 | + </div>; |
| 114 | + } |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +## Advanced Stuff |
| 119 | + |
| 120 | +By default your reducer is called with the type specified in the action only on the success stage, you can listen to pending an fail events too by listening for `getPendingActionType(type)` and `getFailureActionType(type)` types, also your reducer is called every time (pending, success, failure) after the higher order reducer does it's thing, but you can still get the old state as the third parameter (not sure why you would ever need to though) |
| 121 | + |
| 122 | +It's a little weird using the prop of the action creator to inject it's status info into state, but I couldn't really think of a better way to do it (WeakMaps somehow?) |
| 123 | + |
| 124 | +[npm-image]: https://img.shields.io/npm/v/redux-await.svg?style=flat-square |
| 125 | +[npm-url]: https://npmjs.org/package/redux-await |
| 126 | +[travis-image]: https://img.shields.io/travis/symbiont-io/redux-await.svg?style=flat-square |
| 127 | +[travis-url]: https://travis-ci.org/symbiont-io/redux-await |
| 128 | +[coveralls-image]: https://img.shields.io/coveralls/kolodny/redux-await.svg?style=flat-square |
| 129 | +[coveralls-url]: https://coveralls.io/r/kolodny/redux-await |
| 130 | +[downloads-image]: http://img.shields.io/npm/dm/redux-await.svg?style=flat-square |
| 131 | +[downloads-url]: https://npmjs.org/package/redux-await |
0 commit comments