redux-thunk vs redux-saga vs redux-logger vs redux-actions vs redux-observable
State Management Middleware for Redux Comparison
1 Year
redux-thunkredux-sagaredux-loggerredux-actionsredux-observableSimilar Packages:
What's State Management Middleware for Redux?

These npm packages provide various approaches to managing side effects and asynchronous actions in Redux applications. Each package offers unique features and design principles that cater to different use cases, allowing developers to choose the right tool based on their application's requirements and complexity. Understanding the strengths and weaknesses of each package is essential for effective state management in React applications using Redux.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
redux-thunk7,677,06717,76126.8 kB12 years agoMIT
redux-saga1,242,56722,521221 kB432 years agoMIT
redux-logger1,010,5965,748-588 years agoMIT
redux-actions370,9896,49612.5 kB50a year agoMIT
redux-observable272,5817,83169.1 kB692 years agoMIT
Feature Comparison: redux-thunk vs redux-saga vs redux-logger vs redux-actions vs redux-observable

Asynchronous Handling

  • redux-thunk:

    redux-thunk allows you to write action creators that return a function instead of an action. This function can perform asynchronous operations and dispatch actions based on the results, making it a straightforward solution for handling async logic.

  • redux-saga:

    redux-saga uses generator functions to manage asynchronous actions, allowing for more complex control flows like cancellation, debouncing, and sequencing of actions. This makes it suitable for applications with intricate side effects and asynchronous logic.

  • redux-logger:

    redux-logger does not handle asynchronous actions; it simply logs actions and state changes to the console, providing visibility into the flow of your application during development.

  • redux-actions:

    redux-actions does not directly handle asynchronous actions; it focuses on simplifying action creation and type definitions. Asynchronous handling must be managed separately, typically using middleware like redux-thunk or redux-saga.

  • redux-observable:

    redux-observable leverages RxJS to handle asynchronous actions through observables. It allows you to create complex asynchronous workflows by combining multiple streams of events, making it ideal for applications that require intricate event handling.

Complexity

  • redux-thunk:

    redux-thunk is straightforward and easy to understand, making it a good choice for developers who prefer simplicity. It does not introduce significant complexity, allowing for quick integration into existing Redux applications.

  • redux-saga:

    redux-saga can be complex due to its use of generator functions and the need to understand saga effects. While it provides powerful capabilities for managing side effects, it may require additional effort to learn and implement correctly, especially in larger applications.

  • redux-logger:

    redux-logger is also simple to use and does not introduce complexity to your application. It is primarily a debugging tool that can be easily added or removed from your middleware stack without affecting the core functionality of your Redux store.

  • redux-actions:

    redux-actions is relatively simple and lightweight, making it easy to integrate into any Redux application. It reduces boilerplate code but does not add significant complexity to your state management.

  • redux-observable:

    redux-observable introduces complexity due to its reliance on RxJS and the concept of observables. Developers need to understand reactive programming principles to effectively utilize this middleware, which may have a steeper learning curve for those unfamiliar with RxJS.

Testing

  • redux-thunk:

    redux-thunk can be tested by mocking the dispatch function and asserting that the correct actions are dispatched based on the asynchronous logic. This simplicity makes it easy to write tests for async actions.

  • redux-saga:

    redux-saga is designed with testing in mind. Its generator functions can be easily tested by yielding effects and asserting the expected outcomes, making it easier to validate complex side effects and asynchronous logic.

  • redux-logger:

    redux-logger does not require testing as it is a development tool. However, it can be helpful to ensure that it logs the expected actions and state changes during development.

  • redux-actions:

    redux-actions makes testing easier by reducing boilerplate code in action creators. Since actions are simpler, it is easier to write unit tests for them without worrying about complex structures.

  • redux-observable:

    redux-observable can be tested using RxJS testing utilities, allowing you to simulate and assert the behavior of your observables. This makes it easier to write unit tests for complex asynchronous flows, ensuring that your application behaves as expected.

Use Cases

  • redux-thunk:

    redux-thunk is suitable for simpler applications that require basic asynchronous handling, such as fetching data from an API and dispatching the results without the need for complex workflows.

  • redux-saga:

    redux-saga is perfect for applications with complex side effects, such as handling multiple asynchronous requests, managing long-running tasks, or coordinating multiple actions based on user interactions or API responses.

  • redux-logger:

    redux-logger is ideal for development environments where you need to monitor state changes and actions for debugging purposes, helping you understand the flow of your application during development.

  • redux-actions:

    redux-actions is best suited for applications that require a clean and concise way to define actions and action creators without the need for complex asynchronous handling.

  • redux-observable:

    redux-observable is well-suited for applications that involve complex event streams, such as real-time data updates, user interactions, or integrating with external APIs that require reactive programming.

How to Choose: redux-thunk vs redux-saga vs redux-logger vs redux-actions vs redux-observable
  • redux-thunk:

    Select redux-thunk if you want a lightweight middleware for handling asynchronous actions in a straightforward manner. It is suitable for simpler applications where you need to dispatch functions instead of actions, allowing for easy integration of asynchronous logic without the overhead of more complex solutions.

  • redux-saga:

    Choose redux-saga if you need a robust solution for managing side effects in your Redux application, especially when dealing with complex asynchronous flows or long-running tasks. It uses generator functions to handle side effects in a more manageable way, making it easier to test and reason about your asynchronous logic.

  • redux-logger:

    Select redux-logger if you need a simple and effective way to log Redux state changes and actions for debugging purposes. It is ideal for development environments where you want to track state changes in real-time and gain insights into the flow of your application without affecting production performance.

  • redux-actions:

    Choose redux-actions if you want to simplify action creation and reduce boilerplate code in your Redux application. It is particularly useful for projects where you want to streamline action type definitions and action creators, making your code cleaner and easier to maintain.

  • redux-observable:

    Opt for redux-observable if your application requires complex asynchronous workflows and you prefer using RxJS for handling side effects. It is best suited for applications that involve complex event streams, allowing you to leverage the power of observables to manage asynchronous actions in a declarative manner.

README for redux-thunk

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 the default export.

More Details: Importing the thunk middleware

If you're using ES modules:

import thunk from 'redux-thunk' // no changes here 😀

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
      }
    })
})

// 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