Redux Thunk
Thunk middleware for Redux.
npm install --save redux-thunk
Note on 2.x Update
Most tutorials today assume Redux Thunk 1.x so you might run into an issue when running their code with 2.x.
If you use Redux Thunk 2.x in CommonJS environment, don’t forget to add .default
to your import:
- var ReduxThunk = require('redux-thunk')
+ var ReduxThunk = require('redux-thunk').default
If you used ES modules, you’re already all good:
import ReduxThunk from 'redux-thunk'
Additionally, since 2.x, we also support a UMD build:
var ReduxThunk = window.ReduxThunk.default
As you can see, it also requires .default
at the end.
Why Do I Need This?
If you’re not sure whether you need it, you probably don’t.
Read this for an in-depth introduction to thunks in Redux.
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;
Installation
npm install --save redux-thunk
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)
);
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 wife')
).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)
);
}
componentWillReceiveProps(nextProps) {
if (nextProps.forPerson !== this.props.forPerson) {
this.props.dispatch(
makeASandwichWithSecretSauce(nextProps.forPerson)
);
}
}
render() {
return <p>{this.props.sandwiches.join('mustard')}</p>
}
}
export default connect(
state => ({
sandwiches: state.sandwiches
})
)(SandwichShop);
Injecting a Custom Argument
Since 2.1.0, Redux Thunk supports injecting a custom argument using the withExtraArgument
function:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument(api))
)
function fetchUser(id) {
return (dispatch, getState, api) => {
}
}
To pass multiple things, just wrap them in a single object and use destructuring:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ api, whatever }))
)
function fetchUser(id) {
return (dispatch, getState, { api, whatever }) => {
}
}
License
MIT