Redux Thunk
Thunk middleware for Redux. It allows writing functions with logic inside that can interact with a Redux store's dispatch
and getState
methods.
For complete usage instructions and useful patterns, see the Redux docs Writing Logic with Thunks page.
Installation and Setup
Redux Toolkit
If you're using our official Redux Toolkit package as recommended, there's nothing to install - RTK's configureStore
API already adds the thunk middleware by default:
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './features/todos/todosSlice'
import filtersReducer from './features/filters/filtersSlice'
const store = configureStore({
reducer: {
todos: todosReducer,
filters: filtersReducer
}
})
Manual Setup
If you're using the basic Redux createStore
API and need to set this up manually, first add the redux-thunk
package:
npm install redux-thunk
yarn add redux-thunk
The thunk middleware is the default export.
More Details: Importing the thunk middleware
If you're using ES modules:
import thunk from 'redux-thunk'
If you use Redux Thunk 2.x in a CommonJS environment,
don’t forget to add .default
to your import:
- const thunk = require('redux-thunk')
+ const thunk = require('redux-thunk').default
Additionally, since 2.x, we also support a
UMD build for use as a global script tag:
const ReduxThunk = window.ReduxThunk
Then, to enable Redux Thunk, use
applyMiddleware()
:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers/index'
const store = createStore(rootReducer, applyMiddleware(thunk))
Injecting a Custom Argument
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.
For Redux Toolkit, the getDefaultMiddleware
callback inside of configureStore
lets you pass in a custom extraArgument
:
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducer'
import { myCustomApiService } from './api'
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk: {
extraArgument: myCustomApiService
}
})
})
function fetchUser(id) {
return (dispatch, getState, api) => {
}
}
If you need to pass in multiple values, combine them into a single object:
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk: {
extraArgument: {
api: myCustomApiService,
otherValue: 42
}
}
})
})
function fetchUser(id) {
return (dispatch, getState, { api, otherValue }) => {
}
}
If you're setting up the store by hand, the named export withExtraArgument()
function should be used to generate the correct thunk middleware:
const store = createStore(reducer, applyMiddleware(withExtraArgument(api)))
Why Do I Need This?
With a plain basic Redux store, you can only do simple synchronous updates by
dispatching an action. Middleware extends the store's abilities, and lets you
write async logic that interacts with the store.
Thunks are the recommended middleware for basic Redux side effects logic,
including complex synchronous logic that needs access to the store, and simple
async logic like AJAX requests.
For more details on why thunks are useful, see:
You may also want to read the
Redux FAQ entry on choosing which async middleware to use.
While the thunk middleware is not directly included with the Redux core library,
it is used by default in our
@reduxjs/toolkit
package.
Motivation
Redux Thunk middleware
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.
An action creator that returns a function to perform asynchronous dispatch:
const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
function increment() {
return {
type: INCREMENT_COUNTER
}
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment())
}, 1000)
}
}
An action creator that returns a function to perform conditional dispatch:
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState()
if (counter % 2 === 0) {
return
}
dispatch(increment())
}
}
What’s a thunk?!
A thunk is a function that wraps an
expression to delay its evaluation.
let x = 1 + 2
let foo = () => 1 + 2
The term originated as a
humorous past-tense version of "think".
Composition
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:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(rootReducer, applyMiddleware(thunk))
function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
}
function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce
}
}
function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
}
}
function withdrawMoney(amount) {
return {
type: 'WITHDRAW',
amount
}
}
store.dispatch(withdrawMoney(100))
function makeASandwichWithSecretSauce(forPerson) {
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
store.dispatch(makeASandwichWithSecretSauce('Me'))
store.dispatch(makeASandwichWithSecretSauce('My partner')).then(() => {
console.log('Done!')
})
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
return Promise.resolve()
}
return dispatch(makeASandwichWithSecretSauce('My Grandma'))
.then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
)
.then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
.then(() =>
dispatch(
getState().myMoney > 42
? withdrawMoney(42)
: apologize('Me', 'The Sandwich Shop')
)
)
}
}
store
.dispatch(makeSandwichesForEverybody())
.then(() =>
response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
)
import { connect } from 'react-redux'
import { Component } from 'react'
class SandwichShop extends Component {
componentDidMount() {
this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
}
componentDidUpdate(prevProps) {
if (prevProps.forPerson !== this.props.forPerson) {
this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
}
}
render() {
return <p>{this.props.sandwiches.join('mustard')}</p>
}
}
export default connect(state => ({
sandwiches: state.sandwiches
}))(SandwichShop)
License
MIT