Top Related Projects
An alternative side effect model for Redux apps
The Redux Framework
Simple, scalable state management.
A reactive programming library for JavaScript
The official, opinionated, batteries-included toolset for efficient Redux development
Business logic with ease ☄️
Quick Overview
Redux Thunk is a middleware for the Redux state management library that allows you to write asynchronous actions. It provides a way to dispatch actions that perform side effects, such as making API calls, without blocking the main thread.
Pros
- Asynchronous Actions: Redux Thunk enables you to write asynchronous actions, making it easier to handle complex, real-world scenarios that involve fetching data from APIs or performing other time-consuming operations.
- Flexibility: The middleware provides a flexible way to handle side effects, allowing you to customize the behavior of your actions as needed.
- Testability: Separating side effects from the main application logic makes it easier to test your Redux actions and reducers.
- Ecosystem Integration: Redux Thunk is a widely-used and well-supported middleware, making it easy to integrate with other Redux-related libraries and tools.
Cons
- Complexity: While Redux Thunk simplifies the handling of asynchronous actions, it can add some complexity to your codebase, especially for beginners.
- Boilerplate: Writing actions with Redux Thunk can sometimes involve more boilerplate code compared to simpler synchronous actions.
- Lack of Abstraction: Redux Thunk doesn't provide a high-level abstraction for handling asynchronous actions, which can lead to repetitive code across different actions.
- Potential Performance Issues: If not used carefully, Redux Thunk actions can potentially lead to performance issues, such as excessive API calls or unnecessary state updates.
Code Examples
Here are a few examples of how to use Redux Thunk:
- Fetching Data from an API:
import axios from 'axios';
export const fetchData = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await axios.get('/api/data');
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: response.data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error.message });
}
};
};
- Dispatching Multiple Actions:
export const updateUser = (userData) => {
return async (dispatch) => {
dispatch({ type: 'UPDATE_USER_REQUEST' });
try {
const response = await axios.put('/api/users', userData);
dispatch({ type: 'UPDATE_USER_SUCCESS', payload: response.data });
dispatch({ type: 'SHOW_SUCCESS_MESSAGE' });
} catch (error) {
dispatch({ type: 'UPDATE_USER_FAILURE', payload: error.message });
dispatch({ type: 'SHOW_ERROR_MESSAGE' });
}
};
};
- Handling Conditional Logic:
export const toggleTodo = (id) => {
return (dispatch, getState) => {
const todo = getState().todos.find((t) => t.id === id);
dispatch({
type: 'TOGGLE_TODO',
payload: { id, completed: !todo.completed },
});
};
};
Getting Started
To use Redux Thunk in your project, follow these steps:
- Install the
redux-thunk
package:
npm install redux-thunk
- Import the
applyMiddleware
function from Redux and thethunk
middleware:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
- Apply the
thunk
middleware when creating the Redux store:
const store = createStore(rootReducer, applyMiddleware(thunk));
- Now you can use Redux Thunk to write asynchronous actions in your Redux application:
export const fetchData = () => {
return async (dispatch) => {
// Asynchronous action logic here
};
};
That's the basic setup for using Redux Thunk in your project. You can then integrate it with your existing Redux application and start writing more complex, asynchronous actions.
Competitor Comparisons
An alternative side effect model for Redux apps
Pros of redux-saga/redux-saga
- Testability: Redux Saga provides a more testable approach to handling asynchronous actions, making it easier to write and maintain tests for your application.
- Declarative Approach: Sagas use a declarative style, which can make the flow of your application's logic more explicit and easier to understand.
- Handling Errors: Redux Saga provides a more robust way of handling errors, with the ability to catch and handle errors at the saga level.
Cons of redux-saga/redux-saga
- Complexity: Redux Saga has a steeper learning curve compared to Redux Thunk, as it introduces a new set of concepts and APIs.
- Performance: Redux Saga can be more resource-intensive than Redux Thunk, as it uses generators and can create more overhead in your application.
- Boilerplate: Redux Saga requires more boilerplate code to set up and configure, which can make it less suitable for smaller projects.
Code Comparison
Redux Thunk:
export const fetchData = () => {
return (dispatch) => {
dispatch(fetchDataRequest());
fetch('/api/data')
.then((response) => response.json())
.then((data) => dispatch(fetchDataSuccess(data)))
.catch((error) => dispatch(fetchDataFailure(error)));
};
};
Redux Saga:
function* fetchData() {
try {
const data = yield call(fetch, '/api/data');
yield put(fetchDataSuccess(data));
} catch (error) {
yield put(fetchDataFailure(error));
}
}
function* watchFetchData() {
yield takeLatest(FETCH_DATA_REQUEST, fetchData);
}
export default function* rootSaga() {
yield all([watchFetchData()]);
}
The Redux Framework
Pros of Rematch
- Simplified State Management: Rematch provides a more streamlined and opinionated approach to state management, reducing the boilerplate required in traditional Redux setups.
- Automatic Reducers and Actions: Rematch automatically generates reducers and actions based on the defined models, eliminating the need to manually create them.
- Asynchronous Actions: Rematch supports asynchronous actions out of the box, making it easier to handle side effects and API calls.
Cons of Rematch
- Opinionated Approach: Rematch's opinionated nature may not align with the preferences of developers who prefer more flexibility in their state management solution.
- Steeper Learning Curve: Developers who are already familiar with traditional Redux may need to invest more time in understanding Rematch's unique concepts and conventions.
- Smaller Community: Compared to Redux Thunk, Rematch has a smaller community and ecosystem, which may limit the availability of third-party plugins and resources.
Code Comparison
Redux Thunk:
export const fetchData = () => async (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error });
}
};
Rematch:
const model = {
state: { data: null, loading: false, error: null },
reducers: {
fetchDataRequest(state) {
return { ...state, loading: true };
},
fetchDataSuccess(state, payload) {
return { ...state, data: payload, loading: false };
},
fetchDataFailure(state, payload) {
return { ...state, error: payload, loading: false };
},
},
effects: {
async fetchData() {
this.fetchDataRequest();
try {
const response = await fetch('/api/data');
const data = await response.json();
this.fetchDataSuccess(data);
} catch (error) {
this.fetchDataFailure(error);
}
},
},
};
Simple, scalable state management.
Pros of mobxjs/mobx
- Simplicity: MobX provides a more straightforward and intuitive approach to state management, with a focus on observables and reactive programming.
- Performance: MobX is generally considered more performant than Redux-Thunk, as it only updates the necessary parts of the application state.
- Flexibility: MobX allows for a more flexible and decentralized state management approach, making it easier to integrate with existing code.
Cons of mobxjs/mobx
- Steeper Learning Curve: MobX has a slightly steeper learning curve compared to Redux-Thunk, as it introduces new concepts like observables and reactive programming.
- Lack of Ecosystem: The Redux ecosystem is larger and more mature than the MobX ecosystem, with more third-party libraries and tooling available.
- Testability: Testing MobX-based applications can be more challenging compared to Redux-Thunk, as the reactive nature of MobX can make it harder to isolate and test individual components.
Code Comparison
Redux-Thunk:
const fetchData = () => {
return (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
fetch('/api/data')
.then((response) => response.json())
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
})
.catch((error) => {
dispatch({ type: 'FETCH_DATA_FAILURE', error });
});
};
};
MobX:
import { observable, action } from 'mobx';
class DataStore {
@observable data = [];
@action fetchData = async () => {
this.data = await fetch('/api/data').then((response) => response.json());
};
}
A reactive programming library for JavaScript
Pros of ReactiveX/rxjs
- Powerful Reactive Programming: ReactiveX/rxjs provides a powerful reactive programming model, allowing developers to work with asynchronous data streams more effectively.
- Composability: ReactiveX/rxjs offers a rich set of operators that can be composed together to create complex data transformation and processing pipelines.
- Cross-Platform Compatibility: ReactiveX/rxjs is a language-agnostic library, with implementations available for various programming languages, including JavaScript, Java, C#, and more.
Cons of ReactiveX/rxjs
- Steeper Learning Curve: Reactive programming concepts and the RxJS API can be more complex to grasp, especially for developers new to the paradigm.
- Performance Overhead: The reactive programming approach in RxJS can introduce some performance overhead, especially for simple use cases where a more traditional approach might be more efficient.
- Verbosity: RxJS code can sometimes be more verbose compared to simpler state management solutions like Redux Thunk, which may be a concern for some developers.
Code Comparison
Redux Thunk:
export const fetchData = () => async (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error });
}
};
RxJS:
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
export const fetchData = () => ajax('/api/data').pipe(
map((response) => response.response),
catchError((error) => throwError(error))
);
The official, opinionated, batteries-included toolset for efficient Redux development
Pros of Redux Toolkit
- Provides a set of utilities and conventions that help streamline Redux development, reducing boilerplate code.
- Includes features like
createSlice
andcreateAsyncThunk
that simplify common Redux patterns. - Integrates well with TypeScript, providing better type safety and inference.
Cons of Redux Toolkit
- Introduces an additional layer of abstraction, which may be less familiar for developers used to traditional Redux.
- The opinionated nature of Redux Toolkit may not align with all project requirements, limiting flexibility.
- The learning curve for Redux Toolkit may be steeper than for Redux Thunk, especially for beginners.
Code Comparison
Redux Thunk
export const fetchData = () => async (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error });
}
};
Redux Toolkit
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchData = createAsyncThunk('data/fetch', async () => {
const response = await fetch('/api/data');
return await response.json();
});
const dataSlice = createSlice({
name: 'data',
initialState: { status: 'idle', data: null, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
});
export default dataSlice.reducer;
Business logic with ease ☄️
Pros of effector/effector
- Simplicity: Effector provides a more straightforward and concise API compared to Redux Thunk, making it easier to set up and manage state in complex applications.
- Performance: Effector is designed to be highly performant, with features like memoization and batched updates that can improve the overall performance of your application.
- Reactive Approach: Effector takes a reactive approach to state management, which can make it easier to reason about and manage the flow of data in your application.
Cons of effector/effector
- Ecosystem: Redux Thunk has a larger and more established ecosystem, with a wider range of third-party libraries and tools available to extend its functionality.
- Learning Curve: While Effector may be simpler to use than Redux Thunk, it still has a learning curve, and developers who are already familiar with Redux may find it more difficult to transition to Effector.
- Adoption: Redux Thunk has a larger user base and is more widely adopted in the React community, which can make it easier to find resources and support.
Code Comparison
Redux Thunk:
export const fetchData = () => {
return async (dispatch) => {
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch(setData(data));
} catch (error) {
dispatch(setError(error));
}
};
};
Effector:
import { createEvent, createStore, createEffect } from 'effector';
const fetchData = createEffect(async () => {
const response = await fetch('/api/data');
return await response.json();
});
const setData = createEvent();
const setError = createEvent();
const $data = createStore(null)
.on(setData, (_, data) => data)
.on(fetchData.done, (_, { result }) => result);
const $error = createStore(null)
.on(setError, (_, error) => error)
.on(fetchData.fail, (_, { error }) => error);
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
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,
},
})
// The thunk middleware was automatically added
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 a named export.
More Details: Importing the thunk middleware
If you're using ES modules:
import { thunk } from 'redux-thunk'
If you use Redux Thunk in a CommonJS environment:
const { thunk } = require('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))
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,
},
}),
})
// later
function fetchUser(id) {
// The `extraArgument` is the third arg for thunk functions
return (dispatch, getState, api) => {
// you can use api here
}
}
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,
},
},
}),
})
// later
function fetchUser(id) {
return (dispatch, getState, { api, otherValue }) => {
// you can use api and something else here
}
}
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:
-
Redux docs: Writing Logic with Thunks
https://redux.js.org/usage/writing-logic-thunks
The official usage guide page on thunks. Covers why they exist, how the thunk middleware works, and useful patterns for using thunks. -
Stack Overflow: Dispatching Redux Actions with a Timeout
http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559
Dan Abramov explains the basics of managing async behavior in Redux, walking through a progressive series of approaches (inline async calls, async action creators, thunk middleware). -
Stack Overflow: Why do we need middleware for async flow in Redux?
http://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux/34599594#34599594
Dan Abramov gives reasons for using thunks and async middleware, and some useful patterns for using thunks. -
What the heck is a "thunk"?
https://daveceddia.com/what-is-a-thunk/
A quick explanation for what the word "thunk" means in general, and for Redux specifically. -
Thunks in Redux: The Basics
https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60
A detailed look at what thunks are, what they solve, and how to use them.
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(() => {
// Yay! Can invoke sync or async actions with `dispatch`
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.
// calculation of 1 + 2 is immediate
// x === 3
let x = 1 + 2
// calculation of 1 + 2 is delayed
// foo can be called later to perform the calculation
// foo is a thunk!
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'
// Note: this API requires redux@>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk))
function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
}
// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express âfactsâ and not the âasync flowâ.
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,
}
}
// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100))
// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?
// Meet thunks.
// A thunk in this context is a function that can be dispatched to perform async
// activity and can dispatch actions and read state.
// This is an action creator that returns a thunk:
function makeASandwichWithSecretSauce(forPerson) {
// We can invert control here by returning a function - the "thunk".
// When this function is passed to `dispatch`, the thunk middleware will intercept it,
// and call it with `dispatch` and `getState` as arguments.
// This gives the thunk function the ability to run some logic, and still interact with the store.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error)),
)
}
}
// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!
store.dispatch(makeASandwichWithSecretSauce('Me'))
// It even takes care to return the thunkâs return value
// from the dispatch, so I can chain Promises as long as I return them.
store.dispatch(makeASandwichWithSecretSauce('My partner')).then(() => {
console.log('Done!')
})
// In fact I can write action creators that dispatch
// actions and async actions from other action creators,
// and I can build my control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You donât have to return Promises, but itâs a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve()
}
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
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'),
),
)
}
}
// This is very useful for server side rendering, because I can wait
// until data is available, then synchronously render the app.
store
.dispatch(makeSandwichesForEverybody())
.then(() =>
response.send(ReactDOMServer.renderToString(<MyApp store={store} />)),
)
// I can also dispatch a thunk async action from a component
// any time its props change to load the missing data.
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
Top Related Projects
An alternative side effect model for Redux apps
The Redux Framework
Simple, scalable state management.
A reactive programming library for JavaScript
The official, opinionated, batteries-included toolset for efficient Redux development
Business logic with ease ☄️
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot