Thunk Action Creators

A popular technique to inject business logic into redux-enabled apps are thunks. Thunks provide a mechanism to dispatch actions in either a delayed or conditional manor. Typically they are used in conjunction with asynchronous processes, such as a server fetch, etc.

Thunks are introduced by promoting an action that is a function rather than an action object. In other words, the action creator returns a function (i.e. the thunk), that is managed by the redux thunk middleware.

  • A thunk action creator accepts the app-specific contextual business parameters (just like all action creators)
  • and when invoked returns a function (i.e. the thunk)
    • that will in turn be invoked by the redux thunk middleware
    • passing (dispatch, getState) parameters
    • giving the thunk access to both:
      • the contextual business parameters (through closure)
      • and the redux state and dispatch mechanisms

Thunks in action-u

Within action-u, thunk action creators are injected into the ActionStruct using the actionMeta.thunk property.

import {generateActions} from 'action-u';

const actions = generateActions({

  widget: {

    fetch: {      // widget.fetch(selCrit)
                  actionMeta: {
                    thunk(selCrit) {
                      return (dispatch, getState) => {
                        return fetchWidget(selCrit) // ... promise-based async fetch 
                          .then( widget => {
                            dispatch(actions.widget.fetch.complete(widget));
                          })
                          .catch ( err => {
                            dispatch(actions.widget.fetch.fail(err));
                          });
                      };
                    }
                  },

      complete: { // widget.fetch.complete(widget)
                  actionMeta: {
                    traits: ['widget']
                  }
      },

      fail: {     // widget.fetch.fail(err)
                  actionMeta: {
                    traits: ['err']
                  }
      }
    }
  }
});

The actionMeta.thunk property represents the fully implemented action creator function, and is directly injected into the ActionStruct as an ActionNode. In other words, there is NO generation (in this case), rather the entire action creator is fully defined by app-specific code. This merely allows thunk action creators to co-exist with all other generated action creators in the ActionStruct. As a result, when a thunk is used, no other actionMeta properties are allowed.

Note 1: Technically, a thunk is the function returned from a thunk action creator, not the action creator itself. With that said, we simplify our terminology by using the actionMeta.thunk property to refer to the thunk action creator.

Note 2: In reality, you can do anything in your app-defined thunk action creator. In other words, action-u provides no validation of what your action creator does (i.e. returns), rather it merely injects it into the ActionStruct as an ActionNode. By design, it is intended to return a thunk (function), but nothing prevents it from returning, say an action object (you probably wouldn't do this, as it defeats the purpose of auto generating your action creators).

Thunk Modules

Because thunks typically contain several lines of code, it is common to pull them in through a separate module. Here is the same example (from above):

  • actions/thunks/widgetFetch.js

    import actions from '../../actions';
    
    export default function widgetFetch(selCrit) {
      return (dispatch, getState) => {
        return fetchWidget(selCrit) // ... promise-based async fetch 
          .then( widget => {
            dispatch(actions.widget.fetch.complete(widget));
          })
          .catch ( err => {
            dispatch(actions.widget.fetch.fail(err));
          });
      };
    }
    
  • actions/index.js

    import {generateActions} from 'action-u';
    import widgetFetch       from './thunks/widgetFetch';
    
    export default generateActions({
    
      widget: {
    
        fetch: {      // widget.fetch(selCrit)
                      actionMeta: {
                        thunk: widgetFetch
                      },
    
          complete: { // widget.fetch.complete(widget)
                      actionMeta: {
                        traits: ['widget']
                      }
          },
    
          fail: {     // widget.fetch.fail(err)
                      actionMeta: {
                        traits: ['err']
                      }
          }
        }
      }
    });
    

Thunks are merely one way to inject business logic into your redux-enabled application ... there are other techniques!

Thunk support was added to action-u simply to allow thunk action creators to consistently co-exist in the action-u ActionStruct along with all other action creators.

I would encourage you to investigate the alternatives to thunks. This article breaks down the various options: Where do I put my business logic in a React-Redux application? The article is an introduction (and motivation) for the development of redux-logic ... redux middleware for organizing all your business logic.

I have been using redux-logic since it's begining and believe it is the best approach to encapsulate your business logic. Prior to redux-logic, my business logic was spread out in a variety of different places, including:

  • component methods
  • thunks
  • and middleware injections

In addition, I relied heavily on batched actions, where logic entry points would stimulate multiple actions in one procedural chunk of code. Needless to say, this was less than ideal. Even tools like redux-dev-tools could not give me adequate insight into "what was stimulating what"!

All of these techniques were replaced with "true" business logic, organizing all my logic in one isolated spot, all orchestrated by redux-logic!

My business logic is now:

  • located in one logical discipline (i.e. dedicated "logic" modules)
  • making it testable in isolation (very nice)!!
  • has more concise and succinct goals
  • promotes modular reuse
  • provides traceable "cause and effects"
  • is greatly simplified!

results matching ""

    No results matching ""