npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

redux-taking-thunk

v1.0.0

Published

Redux thunk with take latest.

Readme

redux-taking-thunk

A Redux middleware that allows dispatching thunks with takeLeading, takeEvery or takeLatest behaviour, and adds loading state.

Setup

  1. Add the reducer
import { combineReducers } from 'redux'
import { reduxTakingThunkReducer } from 'redux-taking-thunk'

export const reducer = combineReducers({
  other: otherReducer,
  reduxTakingThunk: reduxTakingThunkReducer
})
  1. Add the middleware
// with Redux Toolkit
import {
  configureStore,
  createImmutableStateInvariantMiddleware,
  createSerializableStateInvariantMiddleware
} from '@reduxjs/toolkit'
import thunkMiddleware from 'redux-thunk'
import { reducer } from './reducer'
import { createReduxTakingThunkMiddleware } from 'redux-taking-thunk'

export const store = configureStore({
  reducer,
  middleware: process.env.NODE_ENV !== 'production'
    ? [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
      createImmutableStateInvariantMiddleware(),
      createSerializableStateInvariantMiddleware()
    ]
    : [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
    ],
})
// without Redux Toolkit
import { createStore, applyMiddleware } from 'redux'
import { reducer } from './reducer'
import { createReduxTakingThunkMiddleware } from 'redux-taking-thunk'

export const store = createStore(reducer, undefined, applyMiddleware(createReduxTakingThunkMiddleware()))
// extraArgument
export const store = configureStore({
  reducer,
  middleware: process.env.NODE_ENV !== 'production'
    ? [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
      createImmutableStateInvariantMiddleware(),
      createSerializableStateInvariantMiddleware()
    ]
    : [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
    ],
})

Note about redux-thunk

The redux-taking-thunk middleware only handles the dispatch of a TakingTypeAction, and does not handle dispatch of a function, therefore does not interfere with redux-thunk.

Dispatch API

Adds a dispatch overload that accepts a TakingTypeAction object as parameter and returns a Promise.

dispatch(a: TakingThunkAction) => Promise<any>
const takingThunkAction = {
  name: 'fetchProducts',
  takeType: 'latest',
  thunk: function*(dispatch){
    try {
      const response = yield fetch('http://example.com/products.json')
      dispatch({type: 'fetchProductSuccess', products: response.json()})
    } catch(e) {
      dispatch({type: 'fetchProductsError', error: 'failed to fetch products'})
    }
  }
}
dispatch(takingThunkAction)

TakingTypeAction Properties

  • name
    identifies the action, used to get the loading state
  • takeType
    'every'(default), 'leading', or 'latest'
  • thunk
    a function

takeType

In case of a newly dispatched TakingTypeAction:

If store's state of the name is not "loading", the thunk will execute and store's state will be "loading".

If store's state of the name is "loading", see table:

| takeType | thunk returns a value | thunk returns a Promsie | thunk returns a Generator or Async Generator | | --- | --- | --- | --- | | leading | Does not execute | :star:Does not execute | Does not execute | | every | Executes and increments state's counter.As the function returns immidiately, decrements state's counter | :star:Executes and increments state's counter.After the promise resolves or rejects, decrements state's counter | Executes and increments state's counter.After the generator returns, decrements state's counter | | latest | Does not allow | Does not allow | :star:Executes and changes state's executionId.By changing the executionId, all other running generators will be discontinued(will not call next()) |

:star:: recommended use case

if takeType does not match the store state, the thunk will not be executed.

every does not deal with execution order.

takeType "loading"

| takeType | | | --- | --- | | leading | The leading promise is not resolved/rejected; orThe leading generator is not finished. | every | Not all promises are resolved/rejected; or Not all generators are finished | latest | The latest generator is not finished (does not care about other generators)

thunk

thunk can be

  • a normal function (but no need to use this library),
  • a function that returns a promise(async function),
  • a generator function, or
  • an async generator function

thunk Parameters

  • dispatch
  • getState
  • extraArgument the argument passed in the middleware

thunk return value

  • If thunk is a generator function or an async generator function, and the execution did not finish (because another takeLatest action with same name is dispatched), dispatch will return a resolved Promise with value undefined.

  • If thunk's return value is a promise, dispatch will return a promise with the same resolved or rejected value.

  • If thunk's return value is not a promise, dispatch will return a resolved promise with that value.

  • If an exceptionis thrown from, or uncaught within thunk, dispatch will return a rejected promise with that error.

dispatch Return value

This dispatch overload returns a Promise. (see thunk return value)

E.g. call then on the returned Promise.

dispatch(takingThunkAction).then(() => alert('got porducts!!'))

createIsLoadingSelector API

createIsLoadingSelector creates a selector function of the loading state(see takeType loading) of the actions identified by name.

import { createIsLoadingSelector } from 'redux-taking-thunk'

const isLoadingSelector = createIsLoadingSelector(name)
const isLoading = isLoadingSelector(state)

With Typescript and Redux Toolkit

Type benefits when used with Typescript and Redux Toolkit.

  • Type hint of the dispatch overload
// in store.ts
export type AppDispatch = typeof store.dispatch

// in component
  const dispatch = useAppDispatch()
  dispatch(
  • Type safety when adding middleware, if you forgot to add the reducer
// in store.ts
export const store = configureStore({
  reducer,

  middleware: process.env.NODE_ENV !== 'production'
    ? [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
      createImmutableStateInvariantMiddleware(),
      createSerializableStateInvariantMiddleware()
    ]
    : [
      thunkMiddleware,
      createReduxTakingThunkMiddleware(),
    ],
  // eslint errors: ... Type 'TakingThunkMiddleware...' is not assignable to type 'Middleware...
})

Unit tests

npm test

Motivation

When using Redux Thunk to make API requests, how to handle concurrency?

One way is to just call the thunks, without caring the order of dispatch. The order of promise resolve is not controlled. For example a slow first dispatch, could resolve later than a fast second dispatch.

// take every
async function fetchProducts(dispatch, getsState){
  dispatch({type: 'fetchProductStart'})
  try {
    const response = await fetch('http://example.com/products.json')
    dispatch({type: 'fetchProductSuccess', products: response.json()})
  } catch(e) {
    dispatch({type: 'fetchProductError', error: 'failed to fetch products'})
  }
}

// with redux-thunk
dispatch(fetchProducts) // first
dispatch(fetchProducts) // second, both respond will update state, but do not know which will be the last

A common pattern to handle concurrency is to block the late dispatch if state already is loading.

// take leading
async function fetchProducts(dispatch, getsState){
  if(getsState().productIsLoading === true){
    return
  }
  dispatch({type: 'fetchProductsStart'})
  try {
    const response = await fetch('http://example.com/products.json')
    dispatch({type: 'fetchProductsSuccess', products: response.json()})
  } catch(e) {
    dispatch({type: 'fetchProductsError', error: 'failed to fetch products'})
  }
}

// with redux-thunk
dispatch(fetchProducts) // first
dispatch(fetchProducts) // second, will be blocked if first is not resolved

An interesting way is take latest.

Take latest discontinus, not cancels. E.g. if an API request is made, and while waiting for the promise to resolve, another thunk is dispatched.

We will not try to cancel the previous API request, as it is already made. Instead, we want to skip the execution of any code following the API request.

However, how to skip the code after the API request? We will transform the async function thunk into a generator thunk, so that the execution exits, and re-enters at yield.

// take latest
// not care about the loading state
function* fetchProducts(dispatch, getsState){
  try {
    const response = yield fetch('http://example.com/products.json')
    dispatch({type: 'fetchProductsSuccess', products: response.json()})
  } catch(e) {
    dispatch({type: 'fetchProductsoError', error: 'failed to fetch products'})
  }
}

Because there is a yield, redux-taking-thunk middleware can decide whether or not to re-enter the execution.

For example, with only one dispatch, the thunk executes "normally" as expected, execution "exits" at the yield, after the API request resolves/rejects, execution "re-enters" and executes the following code, and updates state's value.

For another example, the first thunk executes and "exits" at the yield, and waits for the API request to resolve. At this moment, a second thunk is dispatched. The second thunk will execute "normally" until the end (no other dispatches). When the first thunk's API request resolves, the middleware decides not to "re-enter" the first thunk, code following the yield will not be executed, so the state's value will not be overridden.

With redux-taking-thunk the examples will become

// take every
const takingThunkAction = {
  name: 'fetchProducts',
  takeType: 'every',
  thunk: async function(dispatch){
    try {
      const response = await fetch('http://example.com/products.json')
      dispatch({type: 'fetchProductsSuccess', products: response.json()})
    } catch(e) {
      dispatch({type: 'fetchProductsError', error: 'failed to fetch products'})
    }
  }
}
dispatch(takingThunkAction) // first
dispatch(takingThunkAction) // second, both respond will update state, but do not know which will be the last
// take leading
const takingThunkAction = {
  name: 'fetchProducts',
  takeType: 'leading',
  thunk: async function(dispatch){
    try {
      const response = await fetch('http://example.com/products.json')
      dispatch({type: 'fetchProductsoSuccess', products: response.json()})
    } catch(e) {
      dispatch({type: 'fetchProductsError', error: 'failed to fetch products'})
    }
  }
}
dispatch(takingThunkAction) // first, called normally
dispatch(takingThunkAction) // second, not called if first is not resolved
// take latest
const takingThunkAction = {
  name: 'fetchProducts',
  takeType: 'latest',
  thunk: function*(dispatch){
    try {
      const response = yield fetch('http://example.com/products.json')
      dispatch({type: 'fetchProductsSuccess', products: response.json()})
    } catch(e) {
      dispatch({type: 'fetchProductsError', error: 'failed to fetch products'})
    }
  }
}
dispatch(takingThunkAction) // first
dispatch(takingThunkAction) // second, graunteed to be the last to update state

On Idiomatic Redux

Just my 2¢.

Redux pros

  • centralized state
  • separate action from state update logic
    • logic stems from "how the state slice respond the actions?", not "what happens for the action?"
    • vs context, recoil, zustand or global getter setters
  • can reason about state changes by the dispatched actions

Redux cons

  • imposed limitations
  • abstractions

With or without middleware?

Redux without middleware

  • can reason about state changes by the all the dispatched actions

Redux-thunk and/or redux-taking-thunk

  • use dispatch as an extension point
    • dispatch is modified to accept fucntions/thunks
  • can still reason about state changes by the dispatched "simple" actions

Peer Depenedency

  • redux

Depenedencies

  • immer
  • nanoid

Publish to npm

publish

Credit

Original inspiration comes from redux-thunk-loading.