Redux Thunk

repository·master·Indexed 12 days ago

https://github.com/reduxjs/redux-thunk

Middleware for Redux that enables action creators to return functions instead of plain action objects, allowing for asynchronous logic, conditional dispatching, and complex synchronous flows. Version 3.1.0 includes support for injecting custom arguments via withExtraArgument and provides TypeScript types such as ThunkAction, ThunkDispatch, and ThunkMiddleware.

Tokens
3.8K
Snippets
13
Records
14
Agent score
96%

What's inside Redux Thunk

  1. What is a thunk and how does it work?

    master

    A thunk is a middleware that allows you to write action creators that return a function instead of a plain action object. This function receives dispatch and getState as arguments, enabling you to:

    1. Perform asynchronous logic: Delay a dispatch (e.g., after an API call or timeout).
    2. Perform conditional dispatch: Check the current state using getState() before deciding whether to dispatch an action.
    3. Complex synchronous logic: Execute logic that requires access to the store's state or dispatch method.
    const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
    
    function increment() {
      return { type: INCREMENT_COUNTER }
    }
    
    // Example: Async thunk
    function incrementAsync() {
      return dispatch => {
        setTimeout(() => {
          dispatch(increment())
        }, 1000)
      }
    }
    
    // Example: Conditional thunk
    function incrementIfOdd() {
      return (dispatch, getState) => {
        const { counter } = getState()
        if (counter % 2 === 0) {
          return
        }
        dispatch(increment())
      }
    }
  2. Compose asynchronous control flows with thunks

    master

    Redux Thunk allows you to return values from the inner thunk function, which are then passed through as the return value of dispatch. This enables orchestrating complex asynchronous flows by having thunk action creators dispatch each other and returning Promises to wait for completion.

    Key capabilities:

    • Dispatching Thunks: You can dispatch both plain object actions and other thunks within a thunk.
    • Accessing State: Thunks receive getState as an argument, allowing logic to depend on the current state.
    • Promise Chaining: By returning a Promise from the thunk, the caller of store.dispatch(thunk) can use .then() to wait for the asynchronous activity to finish. This is particularly useful for Server Side Rendering (SSR) to ensure data is loaded before rendering.
    import { createStore, applyMiddleware } from 'redux'
    import { thunk } from 'redux-thunk'
    
    // Setup store with thunk middleware
    const store = createStore(rootReducer, applyMiddleware(thunk))
    
    // A thunk action creator
    function makeASandwichWithSecretSauce(forPerson) {
      return function (dispatch) {
        return fetchSecretSauce().then(
          sauce => dispatch(makeASandwich(forPerson, sauce)),
          error => dispatch(apologize('The Sandwich Shop', forPerson, error)),
        )
      }
    }
    
    // Using the thunk and chaining the result
    store.dispatch(makeASandwichWithSecretSauce('My partner')).then(() => {
      console.log('Done!')
    })
    
    // Composing multiple thunks into a single flow
    function makeSandwichesForEverybody() {
      return function (dispatch, getState) {
        if (!getState().sandwiches.isShopOpen) {
          return Promise.resolve()
        }
    
        return dispatch(makeASandwichWithSecretSauce('My Grandma'))
          .then(() =>
            Promise.all([
              dispatch(makeASandwichWithSecretSauce('Me')),
              dispatch(makeASandwichWithSecretSauce('My wife')),
            ]),
          )
          .then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
      }
    }
  3. Setup Redux Thunk with Redux Toolkit

    master

    If you are using @reduxjs/toolkit, you do not need to install anything. The configureStore API includes 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
  4. Setup Redux Thunk manually with createStore

    master

    To enable Redux Thunk when using the standard Redux createStore API, import the thunk named export and pass it to applyMiddleware().

    import { createStore, applyMiddleware } from 'redux'
    import { thunk } from 'redux-thunk'
    import rootReducer from './reducers/index'
    
    const store = createStore(rootReducer, applyMiddleware(thunk))
  5. Dispatch thunks from React components

    master

    When using react-redux, you can dispatch thunk action creators directly from component lifecycle methods (like componentDidMount or componentDidUpdate) via the dispatch prop provided by connect. This allows components to trigger asynchronous data loading when props change.

    import { connect } from 'react-redux'
    import { Component } from 'react'
    
    class SandwichShop extends Component {
      componentDidMount() {
        // Dispatching a thunk on mount
        this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson))
      }
    
      componentDidUpdate(prevProps) {
        // Dispatching a thunk when specific props change
        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)
  6. Inject a custom argument into thunk middleware

    master

    Redux Thunk allows you to inject a custom argument (often an API service layer) into your thunks. This argument is passed as the third parameter to the function returned by your action creator.

    Using with Redux Toolkit

    Use the getDefaultMiddleware callback within configureStore to set the extraArgument property inside the thunk configuration.

    Using with manual setup

    Use the withExtraArgument() function to generate the middleware.

    Accessing the argument in a thunk

    The extraArgument (or an object containing it) is provided as the third argument to the thunk function: (dispatch, getState, extraArgument) => { ... }.

    // Redux Toolkit example with custom API service
    import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
    import rootReducer from './reducer'
    import { myCustomApiService } from './api'
    
    const store = configureStore({
      reducer: rootReducer,
      middleware: getDefaultMiddleware =>
        getDefaultMiddleware({
          thunk: {
            extraArgument: myCustomApiService,
          },
        }),
    })
    
    // Accessing the argument in a thunk
    function fetchUser(id) {
      // The `extraArgument` is the third arg for thunk functions
      return (dispatch, getState, api) => {
        // you can use api here
      }
    }
  7. Inject a custom argument using withExtraArgument

    master

    If you need to provide additional dependencies (like an API client or a router) to your thunks, use the withExtraArgument factory function. This function accepts an extraArgument and returns a ThunkMiddleware instance that will pass that argument as the third parameter to every dispatched thunk function.

    Thunk functions will receive arguments in this order: (dispatch, getState, extraArgument).

    import { withExtraArgument } from 'redux-thunk'
    import { configureStore } from '@reduxjs/toolkit'
    
    const api = { fetchData: () => {} }
    
    // Create middleware with the injected API
    const customThunk = withExtraArgument(api)
    
    const store = configureStore({
      reducer: rootReducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(customThunk),
    })
    
    // Example usage in a thunk:
    // const myThunk = (dispatch, getState, api) => { api.fetchData() }
  8. Use the default thunk middleware

    master

    Import thunk from redux-thunk to use the standard middleware. This middleware allows you to dispatch functions (thunks) instead of just plain action objects. When a function is dispatched, the middleware calls it and injects dispatch and getState as arguments.

    import { thunk } from 'redux-thunk'
    import { configureStore } from '@reduxjs/toolkit'
    
    const store = configureStore({
      reducer: rootReducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(thunk),
    })
  9. Use ThunkMiddleware for Redux middleware setup

    master

    The ThunkMiddleware type represents the middleware function used to enable thunk support in Redux. It is a specialized Middleware type where the dispatch and next types are both ThunkDispatch.

    It accepts three generics:

    • State: The type of the Redux state.
    • BasicAction: The type of standard (non-thunk) actions.
    • ExtraThunkArg: An optional extra argument to pass to thunks (used when calling withExtraArgument()).
    export type ThunkMiddleware<
      State = any,
      BasicAction extends Action = AnyAction,
      ExtraThunkArg = undefined,
    > = Middleware<
      ThunkDispatch<State, ExtraThunkArg, BasicAction>,
      State,
      ThunkDispatch<State, ExtraThunkArg, BasicAction>
    >
  10. Use ThunkDispatch for typed dispatching

    master

    When using redux-thunk, the standard Redux dispatch method is overloaded to support both standard action objects and thunk functions. ThunkDispatch allows you to type this overloaded dispatch method so that TypeScript correctly understands the return values of both types of actions.

    ThunkDispatch accepts three generics:

    • State: The type of the Redux state.
    • ExtraThunkArg: The type of the extra argument passed to thunks (if configured via withExtraArgument).
    • BasicAction: The type of standard (non-thunk) actions that can be dispatched.
    export interface ThunkDispatch<
      State,
      ExtraThunkArg,
      BasicAction extends Action,
    >
  11. Use ThunkActionDispatch for wrapped action creators

    master

    The ThunkActionDispatch type is used to describe the signature of a thunk action creator after it has been processed by functions like bindActionCreators. It maps the arguments of the original action creator to the return type of the thunk's inner function.

    export type ThunkActionDispatch<
      ActionCreator extends (...args: any[]) => ThunkAction<any, any, any, any>,
    > = (
      ...args: Parameters<ActionCreator>
    ) => ReturnType<ReturnType<ActionCreator>>