1 | # redux-actions
|
2 |
|
3 | [![build status](https://img.shields.io/travis/acdlite/redux-actions/master.svg?style=flat-square)](https://travis-ci.org/acdlite/redux-actions)
|
4 |
|
5 | [![NPM](https://nodei.co/npm/redux-actions.png?downloads=true)](https://nodei.co/npm/redux-actions/)
|
6 |
|
7 | [Flux Standard Action](https://github.com/acdlite/flux-standard-action) utilities for Redux.
|
8 |
|
9 | ## Installation
|
10 |
|
11 | ```bash
|
12 | npm install --save redux-actions
|
13 | ```
|
14 |
|
15 | The [npm](https://www.npmjs.com) package provides a [CommonJS](http://webpack.github.io/docs/commonjs.html) build for use in Node.js, and with bundlers like [Webpack](http://webpack.github.io/) and [Browserify](http://browserify.org/). It also includes an [ES modules](http://jsmodules.io/) build that works well with [Rollup](http://rollupjs.org/) and [Webpack2](https://webpack.js.org)'s tree-shaking.
|
16 |
|
17 | If you don’t use [npm](https://www.npmjs.com), you may grab the latest [UMD](https://unpkg.com/redux-actions@latest/dist) build from [unpkg](https://unpkg.com) (either a [development](https://unpkg.com/redux-actions@latest/dist/redux-actions.js) or a [production](https://unpkg.com/redux-actions@latest/dist/redux-actions.min.js) build). The UMD build exports a global called `window.ReduxActions` if you add it to your page via a `<script>` tag. We *don’t* recommend UMD builds for any serious application, as most of the libraries complementary to Redux are only available on [npm](https://www.npmjs.com/search?q=redux).
|
18 |
|
19 | ## Usage
|
20 |
|
21 | ### `createAction(type, payloadCreator = Identity, ?metaCreator)`
|
22 |
|
23 | ```js
|
24 | import { createAction } from 'redux-actions';
|
25 | ```
|
26 |
|
27 | Wraps an action creator so that its return value is the payload of a Flux Standard Action.
|
28 |
|
29 | `payloadCreator` must be a function, `undefined`, or `null`. If `payloadCreator` is `undefined` or `null`, the identity function is used.
|
30 |
|
31 | Example:
|
32 |
|
33 | ```js
|
34 | let increment = createAction('INCREMENT', amount => amount);
|
35 | // same as
|
36 | increment = createAction('INCREMENT');
|
37 |
|
38 | expect(increment(42)).to.deep.equal({
|
39 | type: 'INCREMENT',
|
40 | payload: 42
|
41 | });
|
42 | ```
|
43 |
|
44 | If the payload is an instance of an [Error
|
45 | object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error),
|
46 | redux-actions will automatically set ```action.error``` to true.
|
47 |
|
48 | Example:
|
49 |
|
50 | ```js
|
51 | const increment = createAction('INCREMENT');
|
52 |
|
53 | const error = new TypeError('not a number');
|
54 | expect(increment(error)).to.deep.equal({
|
55 | type: 'INCREMENT',
|
56 | payload: error,
|
57 | error: true
|
58 | });
|
59 | ```
|
60 |
|
61 | `createAction` also returns its `type` when used as type in `handleAction` or `handleActions`.
|
62 |
|
63 | Example:
|
64 |
|
65 | ```js
|
66 | const increment = createAction('INCREMENT');
|
67 |
|
68 | // As parameter in handleAction:
|
69 | handleAction(increment, {
|
70 | next(state, action) {...},
|
71 | throw(state, action) {...}
|
72 | });
|
73 |
|
74 | // As object key in handleActions:
|
75 | const reducer = handleActions({
|
76 | [increment]: (state, action) => ({
|
77 | counter: state.counter + action.payload
|
78 | })
|
79 | }, { counter: 0 });
|
80 | ```
|
81 |
|
82 | **NOTE:** The more correct name for this function is probably `createActionCreator()`, but that seems a bit redundant.
|
83 |
|
84 | Use the identity form to create one-off actions:
|
85 |
|
86 | ```js
|
87 | createAction('ADD_TODO')('Use Redux');
|
88 | ```
|
89 |
|
90 | `metaCreator` is an optional function that creates metadata for the payload. It receives the same arguments as the payload creator, but its result becomes the meta field of the resulting action. If `metaCreator` is undefined or not a function, the meta field is omitted.
|
91 |
|
92 | ### `createActions(?actionsMap, ?...identityActions)`
|
93 |
|
94 | ```js
|
95 | import { createActions } from 'redux-actions';
|
96 | ```
|
97 |
|
98 | Returns an object mapping action types to action creators. The keys of this object are camel-cased from the keys in `actionsMap` and the string literals of `identityActions`; the values are the action creators.
|
99 |
|
100 | `actionsMap` is an optional object with action types as keys, and whose values **must** be either
|
101 |
|
102 | - a function, which is the payload creator for that action
|
103 | - an array with `payload` and `meta` functions in that order, as in [`createAction`](#createactiontype-payloadcreator--identity-metacreator)
|
104 | - `meta` is **required** in this case (otherwise use the function form above)
|
105 |
|
106 | `identityActions` is an optional list of positional string arguments that are action type strings; these action types will use the identity payload creator.
|
107 |
|
108 | ```js
|
109 | const { actionOne, actionTwo, actionThree } = createActions({
|
110 | // function form; payload creator defined inline
|
111 | ACTION_ONE: (key, value) => ({ [key]: value }),
|
112 |
|
113 | // array form
|
114 | ACTION_TWO: [
|
115 | (first) => [first], // payload
|
116 | (first, second) => ({ second }) // meta
|
117 | ],
|
118 |
|
119 | // trailing action type string form; payload creator is the identity
|
120 | }, 'ACTION_THREE');
|
121 |
|
122 | expect(actionOne('key', 1)).to.deep.equal({
|
123 | type: 'ACTION_ONE',
|
124 | payload: { key: 1 }
|
125 | });
|
126 |
|
127 | expect(actionTwo('first', 'second')).to.deep.equal({
|
128 | type: 'ACTION_TWO',
|
129 | payload: ['first'],
|
130 | meta: { second: 'second' }
|
131 | });
|
132 |
|
133 | expect(actionThree(3)).to.deep.equal({
|
134 | type: 'ACTION_THREE',
|
135 | payload: 3,
|
136 | });
|
137 | ```
|
138 |
|
139 | ### `handleAction(type, reducer | reducerMap = Identity, defaultState)`
|
140 |
|
141 | ```js
|
142 | import { handleAction } from 'redux-actions';
|
143 | ```
|
144 |
|
145 | Wraps a reducer so that it only handles Flux Standard Actions of a certain type.
|
146 |
|
147 | If a `reducer` function is passed, it is used to handle both normal actions and failed actions. (A failed action is analogous to a rejected promise.) You can use this form if you know a certain type of action will never fail, like the increment example above.
|
148 |
|
149 | Otherwise, you can specify separate reducers for `next()` and `throw()` using the `reducerMap` form. This API is inspired by the ES6 generator interface.
|
150 |
|
151 | ```js
|
152 | handleAction('FETCH_DATA', {
|
153 | next(state, action) {...},
|
154 | throw(state, action) {...}
|
155 | }, defaultState);
|
156 | ```
|
157 |
|
158 | If either `next()` or `throw()` are `undefined` or `null`, then the identity function is used for that reducer.
|
159 |
|
160 | If the reducer argument (`reducer | reducerMap`) is `undefined`, then the identity function is used.
|
161 |
|
162 | The third parameter `defaultState` is required, and is used when `undefined` is passed to the reducer.
|
163 |
|
164 | ### `handleActions(reducerMap, defaultState)`
|
165 |
|
166 | ```js
|
167 | import { handleActions } from 'redux-actions';
|
168 | ```
|
169 |
|
170 | Creates multiple reducers using `handleAction()` and combines them into a single reducer that handles multiple actions. Accepts a map where the keys are passed as the first parameter to `handleAction()` (the action type), and the values are passed as the second parameter (either a reducer or reducer map). The map must not be empty.
|
171 |
|
172 | The second parameter `defaultState` is required, and is used when `undefined` is passed to the reducer.
|
173 |
|
174 | (Internally, `handleActions()` works by applying multiple reducers in sequence using [reduce-reducers](https://github.com/acdlite/reduce-reducers).)
|
175 |
|
176 | Example:
|
177 |
|
178 | ```js
|
179 | const reducer = handleActions({
|
180 | INCREMENT: (state, action) => ({
|
181 | counter: state.counter + action.payload
|
182 | }),
|
183 |
|
184 | DECREMENT: (state, action) => ({
|
185 | counter: state.counter - action.payload
|
186 | })
|
187 | }, { counter: 0 });
|
188 | ```
|
189 |
|
190 | ### `combineActions(...actionTypes)`
|
191 |
|
192 | Combine any number of action types or action creators. `actionTypes` is a list of positional arguments which can be action type strings, symbols, or action creators.
|
193 |
|
194 | This allows you to reduce multiple distinct actions with the same reducer.
|
195 |
|
196 | ```js
|
197 | const { increment, decrement } = createActions({
|
198 | INCREMENT: amount => ({ amount }),
|
199 | DECREMENT: amount => ({ amount: -amount }),
|
200 | })
|
201 |
|
202 | const reducer = handleAction(combineActions(increment, decrement), {
|
203 | next: (state, { payload: { amount } }) => ({ ...state, counter: state.counter + amount }),
|
204 | throw: state => ({ ...state, counter: 0 }),
|
205 | }, { counter: 10 })
|
206 |
|
207 | expect(reducer(undefined, increment(1)).to.deep.equal({ counter: 11 })
|
208 | expect(reducer(undefined, decrement(1)).to.deep.equal({ counter: 9 })
|
209 | expect(reducer(undefined, increment(new Error)).to.deep.equal({ counter: 0 })
|
210 | expect(reducer(undefined, decrement(new Error)).to.deep.equal({ counter: 0 })
|
211 | ```
|
212 |
|
213 | Here's an example using `handleActions`:
|
214 |
|
215 | ```js
|
216 | const { increment, decrement } = createActions({
|
217 | INCREMENT: amount => ({ amount }),
|
218 | DECREMENT: amount => ({ amount: -amount })
|
219 | });
|
220 |
|
221 | const reducer = handleActions({
|
222 | [combineActions(increment, decrement)](state, { payload: { amount } }) {
|
223 | return { ...state, counter: state.counter + amount };
|
224 | }
|
225 | }, { counter: 10 });
|
226 |
|
227 | expect(reducer({ counter: 5 }, increment(5))).to.deep.equal({ counter: 10 });
|
228 | expect(reducer({ counter: 5 }, decrement(5))).to.deep.equal({ counter: 0 });
|
229 | expect(reducer({ counter: 5 }, { type: 'NOT_TYPE', payload: 1000 })).to.equal({ counter: 5 });
|
230 | expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 });
|
231 | ```
|
232 |
|
233 | ## Usage with middleware
|
234 |
|
235 | redux-actions is handy all by itself, however, its real power comes when you combine it with middleware.
|
236 |
|
237 | The identity form of `createAction` is a great way to create a single action creator that handles multiple payload types. For example, using [redux-promise](https://github.com/acdlite/redux-promise) and [redux-rx](https://github.com/acdlite/redux-rx):
|
238 |
|
239 | ```js
|
240 | const addTodo = createAction('ADD_TODO');
|
241 |
|
242 | // A single reducer...
|
243 | handleAction('ADD_TODO', (state = { todos: [] }, action) => ({
|
244 | ...state,
|
245 | todos: [...state.todos, action.payload]
|
246 | }));
|
247 |
|
248 | // ...that works with all of these forms:
|
249 | // (Don't forget to use `bindActionCreators()` or equivalent.
|
250 | // I've left that bit out)
|
251 | addTodo('Use Redux')
|
252 | addTodo(Promise.resolve('Weep with joy'));
|
253 | addTodo(Observable.of(
|
254 | 'Learn about middleware',
|
255 | 'Learn about higher-order stores'
|
256 | )).subscribe();
|
257 | ```
|
258 |
|
259 | ## See also
|
260 |
|
261 | Use redux-actions in combination with FSA-compliant libraries.
|
262 |
|
263 | - [redux-promise](https://github.com/acdlite/redux-promise) - Promise middleware
|
264 | - [redux-rx](https://github.com/acdlite/redux-rx) - Includes observable middleware.
|