redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools
Redux Middleware and DevTools
redux-thunkredux-sagaredux-loggerredux-devtools-extensionredux-devtoolsSimilar Packages:
Redux Middleware and DevTools

Redux is a popular state management library for JavaScript applications, particularly those built with React. It helps manage the state of an application in a predictable way by using a single store and actions to update the state. Middleware in Redux is a way to extend its capabilities by intercepting actions before they reach the reducer. This allows for additional functionality such as logging, handling asynchronous actions, or integrating with external APIs. The packages redux-devtools, redux-devtools-extension, redux-logger, redux-saga, and redux-thunk are all related to enhancing the Redux development experience, but they serve different purposes. redux-devtools and redux-devtools-extension are tools for debugging and visualizing the state changes in a Redux application. redux-logger is a middleware that logs actions and state changes to the console, making it easier to track what is happening in the application. redux-saga and redux-thunk are middleware for handling asynchronous actions in Redux. redux-thunk allows you to write action creators that return functions instead of actions, enabling delayed dispatching of actions or dispatching based on certain conditions. redux-saga uses generator functions to handle side effects in a more structured and testable way, making it suitable for complex asynchronous workflows.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
redux-thunk9,572,03717,74126.8 kB12 years agoMIT
redux-saga1,259,99722,5046.25 kB44a month agoMIT
redux-logger992,6685,741-589 years agoMIT
redux-devtools-extension828,93713,487-2655 years agoMIT
redux-devtools85,11314,309-2295 years agoMIT
Feature Comparison: redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools

Debugging and Visualization

  • redux-thunk:

    redux-thunk does not provide any special debugging features. It relies on standard Redux tools for inspecting actions and state. Since thunks are just functions, they can be harder to debug compared to sagas, especially in complex scenarios.

  • redux-saga:

    redux-saga does not provide built-in debugging tools, but its structured approach to handling side effects makes it easier to reason about asynchronous code. You can use tools like the Redux DevTools to inspect actions and state changes triggered by sagas.

  • redux-logger:

    redux-logger does not provide a visual interface but logs actions and state changes to the console. This makes it easy to see what is happening in your application without leaving your development environment.

  • redux-devtools-extension:

    redux-devtools-extension integrates the DevTools directly into your browser, providing a more convenient and accessible way to debug your application. It offers the same features as redux-devtools but with a more streamlined setup.

  • redux-devtools:

    redux-devtools provides a standalone interface for visualizing state changes, actions, and the overall state tree. It supports time-travel debugging, allowing you to rewind and replay actions to see how the state evolves over time.

Asynchronous Handling

  • redux-thunk:

    redux-thunk allows you to write async logic directly in your action creators. It is simple and effective for handling basic async operations like fetching data, but it can become harder to manage as the complexity of the async logic increases.

  • redux-saga:

    redux-saga is designed specifically for handling complex asynchronous workflows. It uses generator functions to manage side effects in a more declarative and testable way, making it easier to handle tasks like API calls, retries, and cancellations.

  • redux-logger:

    redux-logger logs all actions, including async actions, making it easier to track the flow of async operations. However, it does not provide any special handling or visualization for async actions.

  • redux-devtools-extension:

    redux-devtools-extension also does not handle async actions directly but integrates with the Redux DevTools to provide real-time visualization of async workflows. This helps developers understand how async actions impact the state over time.

  • redux-devtools:

    redux-devtools does not handle asynchronous actions directly but provides a way to visualize the effects of async actions on the state. This makes it easier to debug async workflows by seeing the sequence of actions and state changes.

Code Complexity

  • redux-thunk:

    redux-thunk is relatively simple to implement and understand. It allows you to keep async logic close to your action creators, but this can lead to scattered and harder-to-maintain code as the application grows.

  • redux-saga:

    redux-saga introduces more complexity due to its use of generator functions and the need to define sagas separately from your reducers and actions. However, this complexity is justified for applications with complex async workflows.

  • redux-logger:

    redux-logger is a simple middleware that can be added to your Redux store with minimal configuration. It does not introduce significant complexity but provides valuable insights during development.

  • redux-devtools-extension:

    redux-devtools-extension is also a non-intrusive tool that integrates seamlessly with your Redux store. It does not require any additional code, making it easy to add to your project.

  • redux-devtools:

    redux-devtools does not add any complexity to your codebase. It is a development tool that integrates with your existing Redux setup without requiring any changes to your code.

Scalability

  • redux-thunk:

    redux-thunk is suitable for small to medium-sized applications with simple async needs. However, as the application grows, managing complex async logic with thunks can become challenging.

  • redux-saga:

    redux-saga is highly scalable and designed for applications with complex side effects. Its structured approach makes it easier to manage and test async workflows as the application grows.

  • redux-logger:

    redux-logger is effective for small to medium-sized applications but can become overwhelming in large applications with a high volume of actions. In such cases, you may need to configure it to filter or group actions.

  • redux-devtools-extension:

    redux-devtools-extension also scales well and provides a convenient way to debug large applications directly from the browser. Its features like action filtering and state diffing help manage complexity.

  • redux-devtools:

    redux-devtools scales well with your application, providing consistent debugging capabilities regardless of the size or complexity of your state. It is particularly useful for large applications where understanding state changes is critical.

Ease of Use: Code Examples

  • redux-thunk:

    Example of using redux-thunk:

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
    // Example of a thunk action creator
    const fetchData = () => {
      return async (dispatch) => {
        dispatch({ type: 'FETCH_DATA_REQUEST' });
        try {
          const response = await fetch('https://api.example.com/data');
          const data = await response.json();
          dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
        } catch (error) {
          dispatch({ type: 'FETCH_DATA_FAILURE', error });
        }
      };
    };
    
  • redux-saga:

    Example of setting up redux-saga:

    import { createStore, applyMiddleware } from 'redux';
    import createSagaMiddleware from 'redux-saga';
    import rootReducer from './reducers';
    import rootSaga from './sagas';
    
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
    sagaMiddleware.run(rootSaga);
    
  • redux-logger:

    Example of adding redux-logger middleware:

    import { createStore, applyMiddleware } from 'redux';
    import rootReducer from './reducers';
    import logger from 'redux-logger';
    
    const store = createStore(rootReducer, applyMiddleware(logger));
    
  • redux-devtools-extension:

    Example of using redux-devtools-extension:

    import { createStore } from 'redux';
    import rootReducer from './reducers';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    const store = createStore(rootReducer, composeWithDevTools());
    
  • redux-devtools:

    Example of integrating redux-devtools:

    import { createStore } from 'redux';
    import rootReducer from './reducers';
    import { composeWithDevTools } from 'redux-devtools-extension';
    
    const store = createStore(rootReducer, composeWithDevTools());
    
How to Choose: redux-thunk vs redux-saga vs redux-logger vs redux-devtools-extension vs redux-devtools
  • redux-thunk:

    Choose redux-thunk if you need a lightweight and straightforward way to handle asynchronous actions. It is easy to implement and works well for simple async operations like fetching data from an API.

  • redux-saga:

    Choose redux-saga if you need a more powerful and scalable solution for handling complex asynchronous workflows. It is ideal for applications with multiple side effects, such as API calls, cancellations, and background tasks.

  • redux-logger:

    Choose redux-logger if you need a simple and effective way to log actions and state changes in your Redux application. It is particularly useful during development for understanding the flow of actions and identifying issues.

  • redux-devtools-extension:

    Choose redux-devtools-extension if you want to integrate the Redux DevTools directly into your browser. This extension provides a seamless debugging experience with features like time-travel, action replay, and state inspection.

  • redux-devtools:

    Choose redux-devtools if you want a standalone tool for inspecting and debugging your Redux state and actions. It provides a comprehensive interface for time-travel debugging and state visualization.

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:

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