What is redux-thunk?
Redux Thunk is a middleware for Redux that allows you to write action creators that return a function instead of an action. This 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.
What are redux-thunk's main functionalities?
Asynchronous Actions
This feature allows for asynchronous actions within Redux. The code sample demonstrates how to create an action that fetches data asynchronously and dispatches different actions based on the result of the fetch.
function fetchData() {
return (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(json => dispatch({ type: 'FETCH_DATA_SUCCESS', payload: json }))
.catch(error => dispatch({ type: 'FETCH_DATA_FAILURE', error }));
};
}
Conditional Dispatching
Redux Thunk allows for conditional dispatching of actions based on the current state or any other condition. The code sample shows how to dispatch an action only if certain conditions are met, using the `getState` method to access the current state.
function updateDataIfNeeded(data) {
return (dispatch, getState) => {
if (shouldUpdateData(getState(), data)) {
dispatch({ type: 'UPDATE_DATA', payload: data });
}
};
}
Other packages similar to redux-thunk
redux-saga
Redux Saga is a library that aims to make application side effects (i.e., asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, and better at handling failures. It uses generators to make those asynchronous flows easy to read, write, and test. Compared to Redux Thunk, Redux Saga provides a more powerful and complex solution for managing side effects, with more control over asynchronous actions.
redux-observable
Redux Observable is middleware for Redux that is used to handle asynchronous actions and side effects using RxJS observables. It allows for more complex asynchronous operations and provides a way to cancel them. Compared to Redux Thunk, Redux Observable offers a more declarative approach to handling side effects through observables, which can be more suitable for applications with complex asynchronous logic.
Redux Thunk
Thunk middleware for Redux.
npm install --save redux-thunk
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;
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());
};
}
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 createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
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';
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
let store = createStoreWithMiddleware(rootReducer);
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(React.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(
SandwichShop,
state => ({
sandwiches: state.sandwiches
})
);
License
MIT