Convert Figma logo to code with AI

reduxjs logoredux-thunk

Thunk middleware for Redux

17,767
1,046
17,767
1

Top Related Projects

An alternative side effect model for Redux apps

8,477

The Redux Framework

27,458

Simple, scalable state management.

30,713

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:

  1. 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 });
    }
  };
};
  1. 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' });
    }
  };
};
  1. 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:

  1. Install the redux-thunk package:
npm install redux-thunk
  1. Import the applyMiddleware function from Redux and the thunk middleware:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
  1. Apply the thunk middleware when creating the Redux store:
const store = createStore(rootReducer, applyMiddleware(thunk));
  1. 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()]);
}
8,477

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);
      }
    },
  },
};
27,458

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());
  };
}
30,713

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 and createAsyncThunk 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 Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

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.

GitHub Workflow Status npm version npm downloads

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:

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

NPM DownloadsLast 30 Days