1 | Redux Thunk
|
2 | =============
|
3 |
|
4 | Thunk [middleware](https://redux.js.org/advanced/middleware) for Redux.
|
5 |
|
6 | [![build status](https://img.shields.io/travis/reduxjs/redux-thunk/master.svg?style=flat-square)](https://travis-ci.org/reduxjs/redux-thunk)
|
7 | [![npm version](https://img.shields.io/npm/v/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
8 | [![npm downloads](https://img.shields.io/npm/dm/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
9 |
|
10 | ```js
|
11 | npm install --save redux-thunk
|
12 | ```
|
13 |
|
14 | ## Note on 2.x Update
|
15 |
|
16 | Most tutorials today assume Redux Thunk 1.x so you might run into an issue when running their code with 2.x.
|
17 | **If you use Redux Thunk 2.x in CommonJS environment, [don’t forget to add `.default` to your import](https://github.com/reduxjs/redux-thunk/releases/tag/v2.0.0):**
|
18 |
|
19 | ```diff
|
20 | - var ReduxThunk = require('redux-thunk')
|
21 | + var ReduxThunk = require('redux-thunk').default
|
22 | ```
|
23 |
|
24 | If you used ES modules, you’re already all good:
|
25 |
|
26 | ```js
|
27 | import ReduxThunk from 'redux-thunk' // no changes here 😀
|
28 | ```
|
29 |
|
30 | Additionally, since 2.x, we also support a [UMD build](https://unpkg.com/redux-thunk/dist/redux-thunk.min.js):
|
31 |
|
32 | ```js
|
33 | var ReduxThunk = window.ReduxThunk.default
|
34 | ```
|
35 |
|
36 | As you can see, it also requires `.default` at the end.
|
37 |
|
38 | ## Why Do I Need This?
|
39 |
|
40 | If you’re not sure whether you need it, you probably don’t.
|
41 |
|
42 | **[Read this for an in-depth introduction to thunks in Redux.](http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559)**
|
43 |
|
44 | ## Motivation
|
45 |
|
46 | Redux Thunk [middleware](https://github.com/reactjs/redux/blob/master/docs/advanced/Middleware.md) allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods `dispatch` and `getState` as parameters.
|
47 |
|
48 | An action creator that returns a function to perform asynchronous dispatch:
|
49 |
|
50 | ```js
|
51 | const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
|
52 |
|
53 | function increment() {
|
54 | return {
|
55 | type: INCREMENT_COUNTER
|
56 | };
|
57 | }
|
58 |
|
59 | function incrementAsync() {
|
60 | return dispatch => {
|
61 | setTimeout(() => {
|
62 | // Yay! Can invoke sync or async actions with `dispatch`
|
63 | dispatch(increment());
|
64 | }, 1000);
|
65 | };
|
66 | }
|
67 | ```
|
68 |
|
69 | An action creator that returns a function to perform conditional dispatch:
|
70 |
|
71 | ```js
|
72 | function incrementIfOdd() {
|
73 | return (dispatch, getState) => {
|
74 | const { counter } = getState();
|
75 |
|
76 | if (counter % 2 === 0) {
|
77 | return;
|
78 | }
|
79 |
|
80 | dispatch(increment());
|
81 | };
|
82 | }
|
83 | ```
|
84 |
|
85 | ## What’s a thunk?!
|
86 |
|
87 | A [thunk](https://en.wikipedia.org/wiki/Thunk) is a function that wraps an expression to delay its evaluation.
|
88 |
|
89 | ```js
|
90 | // calculation of 1 + 2 is immediate
|
91 | // x === 3
|
92 | let x = 1 + 2;
|
93 |
|
94 | // calculation of 1 + 2 is delayed
|
95 | // foo can be called later to perform the calculation
|
96 | // foo is a thunk!
|
97 | let foo = () => 1 + 2;
|
98 | ```
|
99 |
|
100 | The term [originated](https://en.wikipedia.org/wiki/Thunk#cite_note-1) as a humorous past-tense version of "think".
|
101 |
|
102 | ## Installation
|
103 |
|
104 | ```
|
105 | npm install --save redux-thunk
|
106 | ```
|
107 |
|
108 | Then, to enable Redux Thunk, use [`applyMiddleware()`](https://redux.js.org/api-reference/applymiddleware):
|
109 |
|
110 | ```js
|
111 | import { createStore, applyMiddleware } from 'redux';
|
112 | import thunk from 'redux-thunk';
|
113 | import rootReducer from './reducers/index';
|
114 |
|
115 | // Note: this API requires redux@>=3.1.0
|
116 | const store = createStore(
|
117 | rootReducer,
|
118 | applyMiddleware(thunk)
|
119 | );
|
120 | ```
|
121 |
|
122 | ## Composition
|
123 |
|
124 | Any return value from the inner function will be available as the return value of `dispatch` itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other’s completion:
|
125 |
|
126 | ```js
|
127 | import { createStore, applyMiddleware } from 'redux';
|
128 | import thunk from 'redux-thunk';
|
129 | import rootReducer from './reducers';
|
130 |
|
131 | // Note: this API requires redux@>=3.1.0
|
132 | const store = createStore(
|
133 | rootReducer,
|
134 | applyMiddleware(thunk)
|
135 | );
|
136 |
|
137 | function fetchSecretSauce() {
|
138 | return fetch('https://www.google.com/search?q=secret+sauce');
|
139 | }
|
140 |
|
141 | // These are the normal action creators you have seen so far.
|
142 | // The actions they return can be dispatched without any middleware.
|
143 | // However, they only express “facts” and not the “async flow”.
|
144 |
|
145 | function makeASandwich(forPerson, secretSauce) {
|
146 | return {
|
147 | type: 'MAKE_SANDWICH',
|
148 | forPerson,
|
149 | secretSauce
|
150 | };
|
151 | }
|
152 |
|
153 | function apologize(fromPerson, toPerson, error) {
|
154 | return {
|
155 | type: 'APOLOGIZE',
|
156 | fromPerson,
|
157 | toPerson,
|
158 | error
|
159 | };
|
160 | }
|
161 |
|
162 | function withdrawMoney(amount) {
|
163 | return {
|
164 | type: 'WITHDRAW',
|
165 | amount
|
166 | };
|
167 | }
|
168 |
|
169 | // Even without middleware, you can dispatch an action:
|
170 | store.dispatch(withdrawMoney(100));
|
171 |
|
172 | // But what do you do when you need to start an asynchronous action,
|
173 | // such as an API call, or a router transition?
|
174 |
|
175 | // Meet thunks.
|
176 | // A thunk is a function that returns a function.
|
177 | // This is a thunk.
|
178 |
|
179 | function makeASandwichWithSecretSauce(forPerson) {
|
180 |
|
181 | // Invert control!
|
182 | // Return a function that accepts `dispatch` so we can dispatch later.
|
183 | // Thunk middleware knows how to turn thunk async actions into actions.
|
184 |
|
185 | return function (dispatch) {
|
186 | return fetchSecretSauce().then(
|
187 | sauce => dispatch(makeASandwich(forPerson, sauce)),
|
188 | error => dispatch(apologize('The Sandwich Shop', forPerson, error))
|
189 | );
|
190 | };
|
191 | }
|
192 |
|
193 | // Thunk middleware lets me dispatch thunk async actions
|
194 | // as if they were actions!
|
195 |
|
196 | store.dispatch(
|
197 | makeASandwichWithSecretSauce('Me')
|
198 | );
|
199 |
|
200 | // It even takes care to return the thunk’s return value
|
201 | // from the dispatch, so I can chain Promises as long as I return them.
|
202 |
|
203 | store.dispatch(
|
204 | makeASandwichWithSecretSauce('My wife')
|
205 | ).then(() => {
|
206 | console.log('Done!');
|
207 | });
|
208 |
|
209 | // In fact I can write action creators that dispatch
|
210 | // actions and async actions from other action creators,
|
211 | // and I can build my control flow with Promises.
|
212 |
|
213 | function makeSandwichesForEverybody() {
|
214 | return function (dispatch, getState) {
|
215 | if (!getState().sandwiches.isShopOpen) {
|
216 |
|
217 | // You don’t have to return Promises, but it’s a handy convention
|
218 | // so the caller can always call .then() on async dispatch result.
|
219 |
|
220 | return Promise.resolve();
|
221 | }
|
222 |
|
223 | // We can dispatch both plain object actions and other thunks,
|
224 | // which lets us compose the asynchronous actions in a single flow.
|
225 |
|
226 | return dispatch(
|
227 | makeASandwichWithSecretSauce('My Grandma')
|
228 | ).then(() =>
|
229 | Promise.all([
|
230 | dispatch(makeASandwichWithSecretSauce('Me')),
|
231 | dispatch(makeASandwichWithSecretSauce('My wife'))
|
232 | ])
|
233 | ).then(() =>
|
234 | dispatch(makeASandwichWithSecretSauce('Our kids'))
|
235 | ).then(() =>
|
236 | dispatch(getState().myMoney > 42 ?
|
237 | withdrawMoney(42) :
|
238 | apologize('Me', 'The Sandwich Shop')
|
239 | )
|
240 | );
|
241 | };
|
242 | }
|
243 |
|
244 | // This is very useful for server side rendering, because I can wait
|
245 | // until data is available, then synchronously render the app.
|
246 |
|
247 | store.dispatch(
|
248 | makeSandwichesForEverybody()
|
249 | ).then(() =>
|
250 | response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
|
251 | );
|
252 |
|
253 | // I can also dispatch a thunk async action from a component
|
254 | // any time its props change to load the missing data.
|
255 |
|
256 | import { connect } from 'react-redux';
|
257 | import { Component } from 'react';
|
258 |
|
259 | class SandwichShop extends Component {
|
260 | componentDidMount() {
|
261 | this.props.dispatch(
|
262 | makeASandwichWithSecretSauce(this.props.forPerson)
|
263 | );
|
264 | }
|
265 |
|
266 | componentDidUpdate(prevProps) {
|
267 | if (prevProps.forPerson !== this.props.forPerson) {
|
268 | this.props.dispatch(
|
269 | makeASandwichWithSecretSauce(this.props.forPerson)
|
270 | );
|
271 | }
|
272 | }
|
273 |
|
274 | render() {
|
275 | return <p>{this.props.sandwiches.join('mustard')}</p>
|
276 | }
|
277 | }
|
278 |
|
279 | export default connect(
|
280 | state => ({
|
281 | sandwiches: state.sandwiches
|
282 | })
|
283 | )(SandwichShop);
|
284 | ```
|
285 |
|
286 | ## Injecting a Custom Argument
|
287 |
|
288 | Since 2.1.0, Redux Thunk supports injecting a custom argument using the `withExtraArgument` function:
|
289 |
|
290 | ```js
|
291 | const store = createStore(
|
292 | reducer,
|
293 | applyMiddleware(thunk.withExtraArgument(api))
|
294 | )
|
295 |
|
296 | // later
|
297 | function fetchUser(id) {
|
298 | return (dispatch, getState, api) => {
|
299 | // you can use api here
|
300 | }
|
301 | }
|
302 | ```
|
303 |
|
304 | To pass multiple things, just wrap them in a single object and use destructuring:
|
305 |
|
306 | ```js
|
307 | const store = createStore(
|
308 | reducer,
|
309 | applyMiddleware(thunk.withExtraArgument({ api, whatever }))
|
310 | )
|
311 |
|
312 | // later
|
313 | function fetchUser(id) {
|
314 | return (dispatch, getState, { api, whatever }) => {
|
315 | // you can use api and something else here
|
316 | }
|
317 | }
|
318 | ```
|
319 |
|
320 |
|
321 | ## License
|
322 |
|
323 | MIT
|