1 | # Redux Thunk
|
2 |
|
3 | Thunk [middleware](https://redux.js.org/tutorials/fundamentals/part-4-store#middleware) for Redux. It allows writing functions with logic inside that can interact with a Redux store's `dispatch` and `getState` methods.
|
4 |
|
5 | For complete usage instructions and useful patterns, see the [Redux docs **Writing Logic with Thunks** page](https://redux.js.org/usage/writing-logic-thunks).
|
6 |
|
7 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/reduxjs/redux-thunk/Tests)
|
8 | [![npm version](https://img.shields.io/npm/v/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
9 | [![npm downloads](https://img.shields.io/npm/dm/redux-thunk.svg?style=flat-square)](https://www.npmjs.com/package/redux-thunk)
|
10 |
|
11 | ## Installation and Setup
|
12 |
|
13 | ### Redux Toolkit
|
14 |
|
15 | If you're using [our official Redux Toolkit package](https://redux-toolkit.js.org) as recommended, there's nothing to install - RTK's `configureStore` API already adds the thunk middleware by default:
|
16 |
|
17 | ```js
|
18 | import { configureStore } from '@reduxjs/toolkit'
|
19 |
|
20 | import todosReducer from './features/todos/todosSlice'
|
21 | import filtersReducer from './features/filters/filtersSlice'
|
22 |
|
23 | const store = configureStore({
|
24 | reducer: {
|
25 | todos: todosReducer,
|
26 | filters: filtersReducer
|
27 | }
|
28 | })
|
29 |
|
30 | // The thunk middleware was automatically added
|
31 | ```
|
32 |
|
33 | ### Manual Setup
|
34 |
|
35 | If you're using the basic Redux `createStore` API and need to set this up manually, first add the `redux-thunk` package:
|
36 |
|
37 | ```sh
|
38 | npm install redux-thunk
|
39 |
|
40 | yarn add redux-thunk
|
41 | ```
|
42 |
|
43 | The thunk middleware is the default export.
|
44 |
|
45 | <details>
|
46 | <summary><b>More Details: Importing the thunk middleware</b></summary>
|
47 |
|
48 | If you're using ES modules:
|
49 |
|
50 | ```js
|
51 | import thunk from 'redux-thunk' // no changes here đ
|
52 | ```
|
53 |
|
54 | If you use Redux Thunk 2.x in a CommonJS environment,
|
55 | [donât forget to add `.default` to your import](https://github.com/reduxjs/redux-thunk/releases/tag/v2.0.0):
|
56 |
|
57 | ```diff
|
58 | - const thunk = require('redux-thunk')
|
59 | + const thunk = require('redux-thunk').default
|
60 | ```
|
61 |
|
62 | Additionally, since 2.x, we also support a
|
63 | [UMD build](https://unpkg.com/redux-thunk/dist/redux-thunk.min.js) for use as a global script tag:
|
64 |
|
65 | ```js
|
66 | const ReduxThunk = window.ReduxThunk
|
67 | ```
|
68 |
|
69 | </details>
|
70 |
|
71 | Then, to enable Redux Thunk, use
|
72 | [`applyMiddleware()`](https://redux.js.org/api/applymiddleware):
|
73 |
|
74 | ```js
|
75 | import { createStore, applyMiddleware } from 'redux'
|
76 | import thunk from 'redux-thunk'
|
77 | import rootReducer from './reducers/index'
|
78 |
|
79 | const store = createStore(rootReducer, applyMiddleware(thunk))
|
80 | ```
|
81 |
|
82 | ### Injecting a Custom Argument
|
83 |
|
84 | Since 2.1.0, Redux Thunk supports injecting a custom argument into the thunk middleware. This is typically useful for cases like using an API service layer that could be swapped out for a mock service in tests.
|
85 |
|
86 | For Redux Toolkit, the `getDefaultMiddleware` callback inside of `configureStore` lets you pass in a custom `extraArgument`:
|
87 |
|
88 | ```js
|
89 | import { configureStore } from '@reduxjs/toolkit'
|
90 | import rootReducer from './reducer'
|
91 | import { myCustomApiService } from './api'
|
92 |
|
93 | const store = configureStore({
|
94 | reducer: rootReducer,
|
95 | middleware: getDefaultMiddleware =>
|
96 | getDefaultMiddleware({
|
97 | thunk: {
|
98 | extraArgument: myCustomApiService
|
99 | }
|
100 | })
|
101 | })
|
102 |
|
103 | // later
|
104 | function fetchUser(id) {
|
105 | // The `extraArgument` is the third arg for thunk functions
|
106 | return (dispatch, getState, api) => {
|
107 | // you can use api here
|
108 | }
|
109 | }
|
110 | ```
|
111 |
|
112 | If you need to pass in multiple values, combine them into a single object:
|
113 |
|
114 | ```js
|
115 | const store = configureStore({
|
116 | reducer: rootReducer,
|
117 | middleware: getDefaultMiddleware =>
|
118 | getDefaultMiddleware({
|
119 | thunk: {
|
120 | extraArgument: {
|
121 | api: myCustomApiService,
|
122 | otherValue: 42
|
123 | }
|
124 | }
|
125 | })
|
126 | })
|
127 |
|
128 | // later
|
129 | function fetchUser(id) {
|
130 | return (dispatch, getState, { api, otherValue }) => {
|
131 | // you can use api and something else here
|
132 | }
|
133 | }
|
134 | ```
|
135 |
|
136 | If you're setting up the store by hand, the default `thunk` export has an attached `thunk.withExtraArgument()` function that should be used to generate the correct thunk middleware:
|
137 |
|
138 | ```js
|
139 | const store = createStore(
|
140 | reducer,
|
141 | applyMiddleware(thunk.withExtraArgument(api))
|
142 | )
|
143 | ```
|
144 |
|
145 | ## Why Do I Need This?
|
146 |
|
147 | With a plain basic Redux store, you can only do simple synchronous updates by
|
148 | dispatching an action. Middleware extends the store's abilities, and lets you
|
149 | write async logic that interacts with the store.
|
150 |
|
151 | Thunks are the recommended middleware for basic Redux side effects logic,
|
152 | including complex synchronous logic that needs access to the store, and simple
|
153 | async logic like AJAX requests.
|
154 |
|
155 | For more details on why thunks are useful, see:
|
156 |
|
157 | - **Redux docs: Writing Logic with Thunks**
|
158 | https://redux.js.org/usage/writing-logic-thunks
|
159 | The official usage guide page on thunks. Covers why they exist, how the thunk middleware works, and useful patterns for using thunks.
|
160 |
|
161 | - **Stack Overflow: Dispatching Redux Actions with a Timeout**
|
162 | http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559
|
163 | Dan Abramov explains the basics of managing async behavior in Redux, walking
|
164 | through a progressive series of approaches (inline async calls, async action
|
165 | creators, thunk middleware).
|
166 |
|
167 | - **Stack Overflow: Why do we need middleware for async flow in Redux?**
|
168 | http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
|
169 | Dan Abramov gives reasons for using thunks and async middleware, and some
|
170 | useful patterns for using thunks.
|
171 |
|
172 | - **What the heck is a "thunk"?**
|
173 | https://daveceddia.com/what-is-a-thunk/
|
174 | A quick explanation for what the word "thunk" means in general, and for Redux
|
175 | specifically.
|
176 |
|
177 | - **Thunks in Redux: The Basics**
|
178 | https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60
|
179 | A detailed look at what thunks are, what they solve, and how to use them.
|
180 |
|
181 | You may also want to read the
|
182 | **[Redux FAQ entry on choosing which async middleware to use](https://redux.js.org/faq/actions#what-async-middleware-should-i-use-how-do-you-decide-between-thunks-sagas-observables-or-something-else)**.
|
183 |
|
184 | While the thunk middleware is not directly included with the Redux core library,
|
185 | it is used by default in our
|
186 | **[`@reduxjs/toolkit` package](https://github.com/reduxjs/redux-toolkit)**.
|
187 |
|
188 | ## Motivation
|
189 |
|
190 | Redux Thunk [middleware](https://redux.js.org/tutorials/fundamentals/part-4-store#middleware)
|
191 | allows you to write action creators that return a function instead of an action.
|
192 | The thunk can be used to delay the dispatch of an action, or to dispatch only if
|
193 | a certain condition is met. The inner function receives the store methods
|
194 | `dispatch` and `getState` as parameters.
|
195 |
|
196 | An action creator that returns a function to perform asynchronous dispatch:
|
197 |
|
198 | ```js
|
199 | const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
|
200 |
|
201 | function increment() {
|
202 | return {
|
203 | type: INCREMENT_COUNTER
|
204 | }
|
205 | }
|
206 |
|
207 | function incrementAsync() {
|
208 | return dispatch => {
|
209 | setTimeout(() => {
|
210 | // Yay! Can invoke sync or async actions with `dispatch`
|
211 | dispatch(increment())
|
212 | }, 1000)
|
213 | }
|
214 | }
|
215 | ```
|
216 |
|
217 | An action creator that returns a function to perform conditional dispatch:
|
218 |
|
219 | ```js
|
220 | function incrementIfOdd() {
|
221 | return (dispatch, getState) => {
|
222 | const { counter } = getState()
|
223 |
|
224 | if (counter % 2 === 0) {
|
225 | return
|
226 | }
|
227 |
|
228 | dispatch(increment())
|
229 | }
|
230 | }
|
231 | ```
|
232 |
|
233 | ## Whatâs a thunk?!
|
234 |
|
235 | A [thunk](https://en.wikipedia.org/wiki/Thunk) is a function that wraps an
|
236 | expression to delay its evaluation.
|
237 |
|
238 | ```js
|
239 | // calculation of 1 + 2 is immediate
|
240 | // x === 3
|
241 | let x = 1 + 2
|
242 |
|
243 | // calculation of 1 + 2 is delayed
|
244 | // foo can be called later to perform the calculation
|
245 | // foo is a thunk!
|
246 | let foo = () => 1 + 2
|
247 | ```
|
248 |
|
249 | The term [originated](https://en.wikipedia.org/wiki/Thunk#cite_note-1) as a
|
250 | humorous past-tense version of "think".
|
251 |
|
252 | ## Composition
|
253 |
|
254 | Any return value from the inner function will be available as the return value
|
255 | of `dispatch` itself. This is convenient for orchestrating an asynchronous
|
256 | control flow with thunk action creators dispatching each other and returning
|
257 | Promises to wait for each otherâs completion:
|
258 |
|
259 | ```js
|
260 | import { createStore, applyMiddleware } from 'redux'
|
261 | import thunk from 'redux-thunk'
|
262 | import rootReducer from './reducers'
|
263 |
|
264 | // Note: this API requires redux@>=3.1.0
|
265 | const store = createStore(rootReducer, applyMiddleware(thunk))
|
266 |
|
267 | function fetchSecretSauce() {
|
268 | return fetch('https://www.google.com/search?q=secret+sauce')
|
269 | }
|
270 |
|
271 | // These are the normal action creators you have seen so far.
|
272 | // The actions they return can be dispatched without any middleware.
|
273 | // However, they only express âfactsâ and not the âasync flowâ.
|
274 |
|
275 | function makeASandwich(forPerson, secretSauce) {
|
276 | return {
|
277 | type: 'MAKE_SANDWICH',
|
278 | forPerson,
|
279 | secretSauce
|
280 | }
|
281 | }
|
282 |
|
283 | function apologize(fromPerson, toPerson, error) {
|
284 | return {
|
285 | type: 'APOLOGIZE',
|
286 | fromPerson,
|
287 | toPerson,
|
288 | error
|
289 | }
|
290 | }
|
291 |
|
292 | function withdrawMoney(amount) {
|
293 | return {
|
294 | type: 'WITHDRAW',
|
295 | amount
|
296 | }
|
297 | }
|
298 |
|
299 | // Even without middleware, you can dispatch an action:
|
300 | store.dispatch(withdrawMoney(100))
|
301 |
|
302 | // But what do you do when you need to start an asynchronous action,
|
303 | // such as an API call, or a router transition?
|
304 |
|
305 | // Meet thunks.
|
306 | // A thunk in this context is a function that can be dispatched to perform async
|
307 | // activity and can dispatch actions and read state.
|
308 | // This is an action creator that returns a thunk:
|
309 | function makeASandwichWithSecretSauce(forPerson) {
|
310 | // We can invert control here by returning a function - the "thunk".
|
311 | // When this function is passed to `dispatch`, the thunk middleware will intercept it,
|
312 | // and call it with `dispatch` and `getState` as arguments.
|
313 | // This gives the thunk function the ability to run some logic, and still interact with the store.
|
314 | return function (dispatch) {
|
315 | return fetchSecretSauce().then(
|
316 | sauce => dispatch(makeASandwich(forPerson, sauce)),
|
317 | error => dispatch(apologize('The Sandwich Shop', forPerson, error))
|
318 | )
|
319 | }
|
320 | }
|
321 |
|
322 | // Thunk middleware lets me dispatch thunk async actions
|
323 | // as if they were actions!
|
324 |
|
325 | store.dispatch(makeASandwichWithSecretSauce('Me'))
|
326 |
|
327 | // It even takes care to return the thunkâs return value
|
328 | // from the dispatch, so I can chain Promises as long as I return them.
|
329 |
|
330 | store.dispatch(makeASandwichWithSecretSauce('My partner')).then(() => {
|
331 | console.log('Done!')
|
332 | })
|
333 |
|
334 | // In fact I can write action creators that dispatch
|
335 | // actions and async actions from other action creators,
|
336 | // and I can build my control flow with Promises.
|
337 |
|
338 | function makeSandwichesForEverybody() {
|
339 | return function (dispatch, getState) {
|
340 | if (!getState().sandwiches.isShopOpen) {
|
341 | // You donât have to return Promises, but itâs a handy convention
|
342 | // so the caller can always call .then() on async dispatch result.
|
343 |
|
344 | return Promise.resolve()
|
345 | }
|
346 |
|
347 | // We can dispatch both plain object actions and other thunks,
|
348 | // which lets us compose the asynchronous actions in a single flow.
|
349 |
|
350 | return dispatch(makeASandwichWithSecretSauce('My Grandma'))
|
351 | .then(() =>
|
352 | Promise.all([
|
353 | dispatch(makeASandwichWithSecretSauce('Me')),
|
354 | dispatch(makeASandwichWithSecretSauce('My wife'))
|
355 | ])
|
356 | )
|
357 | .then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
|
358 | .then(() =>
|
359 | dispatch(
|
360 | getState().myMoney > 42
|
361 | ? withdrawMoney(42)
|
362 | : apologize('Me', 'The Sandwich Shop')
|
363 | )
|
364 | )
|
365 | }
|
366 | }
|
367 |
|
368 | // This is very useful for server side rendering, because I can wait
|
369 | // until data is available, then synchronously render the app.
|
370 |
|
371 | store
|
372 | .dispatch(makeSandwichesForEverybody())
|
373 | .then(() =>
|
374 | response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
|
375 | )
|
376 |
|
377 | // I can also dispatch a thunk async action from a component
|
378 | // any time its props change to load the missing data.
|
379 |
|
380 | import { connect } from 'react-redux'
|
381 | import { Component } from 'react'
|
382 |
|
383 | class SandwichShop extends Component {
|
384 | componentDidMount() {
|
385 | this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
|
386 | }
|
387 |
|
388 | componentDidUpdate(prevProps) {
|
389 | if (prevProps.forPerson !== this.props.forPerson) {
|
390 | this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
|
391 | }
|
392 | }
|
393 |
|
394 | render() {
|
395 | return <p>{this.props.sandwiches.join('mustard')}</p>
|
396 | }
|
397 | }
|
398 |
|
399 | export default connect(state => ({
|
400 | sandwiches: state.sandwiches
|
401 | }))(SandwichShop)
|
402 | ```
|
403 |
|
404 | ## License
|
405 |
|
406 | MIT
|