UNPKG

12.8 kBMarkdownView Raw
1# Redux Thunk
2
3Thunk [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
5For 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
15If 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
18import { configureStore } from '@reduxjs/toolkit'
19
20import todosReducer from './features/todos/todosSlice'
21import filtersReducer from './features/filters/filtersSlice'
22
23const 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
35If you're using the basic Redux `createStore` API and need to set this up manually, first add the `redux-thunk` package:
36
37```sh
38npm install redux-thunk
39
40yarn add redux-thunk
41```
42
43The thunk middleware is the default export.
44
45<details>
46<summary><b>More Details: Importing the thunk middleware</b></summary>
47
48If you're using ES modules:
49
50```js
51import thunk from 'redux-thunk' // no changes here 😀
52```
53
54If 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
62Additionally, 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
66const ReduxThunk = window.ReduxThunk
67```
68
69</details>
70
71Then, to enable Redux Thunk, use
72[`applyMiddleware()`](https://redux.js.org/api/applymiddleware):
73
74```js
75import { createStore, applyMiddleware } from 'redux'
76import thunk from 'redux-thunk'
77import rootReducer from './reducers/index'
78
79const store = createStore(rootReducer, applyMiddleware(thunk))
80```
81
82### Injecting a Custom Argument
83
84Since 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
86For Redux Toolkit, the `getDefaultMiddleware` callback inside of `configureStore` lets you pass in a custom `extraArgument`:
87
88```js
89import { configureStore } from '@reduxjs/toolkit'
90import rootReducer from './reducer'
91import { myCustomApiService } from './api'
92
93const store = configureStore({
94 reducer: rootReducer,
95 middleware: getDefaultMiddleware =>
96 getDefaultMiddleware({
97 thunk: {
98 extraArgument: myCustomApiService
99 }
100 })
101})
102
103// later
104function 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
112If you need to pass in multiple values, combine them into a single object:
113
114```js
115const 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
129function fetchUser(id) {
130 return (dispatch, getState, { api, otherValue }) => {
131 // you can use api and something else here
132 }
133}
134```
135
136If 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
139const store = createStore(
140 reducer,
141 applyMiddleware(thunk.withExtraArgument(api))
142)
143```
144
145## Why Do I Need This?
146
147With a plain basic Redux store, you can only do simple synchronous updates by
148dispatching an action. Middleware extends the store's abilities, and lets you
149write async logic that interacts with the store.
150
151Thunks are the recommended middleware for basic Redux side effects logic,
152including complex synchronous logic that needs access to the store, and simple
153async logic like AJAX requests.
154
155For 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
181You 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
184While the thunk middleware is not directly included with the Redux core library,
185it is used by default in our
186**[`@reduxjs/toolkit` package](https://github.com/reduxjs/redux-toolkit)**.
187
188## Motivation
189
190Redux Thunk [middleware](https://redux.js.org/tutorials/fundamentals/part-4-store#middleware)
191allows you to write action creators that return a function instead of an action.
192The thunk can be used to delay the dispatch of an action, or to dispatch only if
193a certain condition is met. The inner function receives the store methods
194`dispatch` and `getState` as parameters.
195
196An action creator that returns a function to perform asynchronous dispatch:
197
198```js
199const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
200
201function increment() {
202 return {
203 type: INCREMENT_COUNTER
204 }
205}
206
207function 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
217An action creator that returns a function to perform conditional dispatch:
218
219```js
220function 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
235A [thunk](https://en.wikipedia.org/wiki/Thunk) is a function that wraps an
236expression to delay its evaluation.
237
238```js
239// calculation of 1 + 2 is immediate
240// x === 3
241let 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!
246let foo = () => 1 + 2
247```
248
249The term [originated](https://en.wikipedia.org/wiki/Thunk#cite_note-1) as a
250humorous past-tense version of "think".
251
252## Composition
253
254Any return value from the inner function will be available as the return value
255of `dispatch` itself. This is convenient for orchestrating an asynchronous
256control flow with thunk action creators dispatching each other and returning
257Promises to wait for each other’s completion:
258
259```js
260import { createStore, applyMiddleware } from 'redux'
261import thunk from 'redux-thunk'
262import rootReducer from './reducers'
263
264// Note: this API requires redux@>=3.1.0
265const store = createStore(rootReducer, applyMiddleware(thunk))
266
267function 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
275function makeASandwich(forPerson, secretSauce) {
276 return {
277 type: 'MAKE_SANDWICH',
278 forPerson,
279 secretSauce
280 }
281}
282
283function apologize(fromPerson, toPerson, error) {
284 return {
285 type: 'APOLOGIZE',
286 fromPerson,
287 toPerson,
288 error
289 }
290}
291
292function withdrawMoney(amount) {
293 return {
294 type: 'WITHDRAW',
295 amount
296 }
297}
298
299// Even without middleware, you can dispatch an action:
300store.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:
309function 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
325store.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
330store.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
338function 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
371store
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
380import { connect } from 'react-redux'
381import { Component } from 'react'
382
383class 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
399export default connect(state => ({
400 sandwiches: state.sandwiches
401}))(SandwichShop)
402```
403
404## License
405
406MIT