# Combining Synchronous Actions Using Redux Thunk

9 July 2018 · [Redux](https://devinschulz.com/tags/redux/index.md), [Testing](https://devinschulz.com/tags/testing/index.md), [React](https://devinschulz.com/tags/react/index.md), [Jest](https://devinschulz.com/tags/jest/index.md)

Learn how to use Redux Thunk for handling several synchronous actions at once to modify different areas of the application state.

Archived writing, published in 2018. I’ve fixed some grammar since, but otherwise it’s as I wrote it, so some details may have aged.

---

Simple frontend applications I've worked with have one event (click, keypress,
input change, etc.) that dispatches a single action to modify part of the
application state tree. As your application grows in complexity, that single event may need to perform several actions at once and run some sort of business logic before they are dispatched.

## A Potential Solution

Recently I've been using [Redux Thunk](https://github.com/reduxjs/redux-thunk)
for dispatching a single thunk action, and in turn, it dispatches several
actions to modify independent areas of the application state. Think Redux Thunk
is only for asynchronous actions? Think again! Never heard of Redux Thunk or the
term thunk? No worries, I will go a bit more in-depth about how it works below.

Now let's pretend you have an e-commerce store, and your application state looks
something like this:

```javascript
 {
  cart: [],
  view: {
    sidebarOpen: false
  },
  merchandise: [
    {
      id: 1,
      price: 24.99,
      type: 'shirt'
    }
  ]
}
```

Imagine you have a sidebar that slides out from the side of the screen whenever
a user clicks a button to add an item to the cart. When doing so, you might
dispatch two separate actions: one for adding the item to the cart, and the
second for telling the view that the sidebar should be opened.

```javascript
// actions.js
const openSidebar = () => ({
  type: "OPEN_SIDEBAR",
});

const addToCart = (id) => ({
  type: "ADD_TO_CART",
  payload: id,
});
```

```javascript
// AddToCart.jsx
const AddToCart = ({ addToCart, id, openSidebar }) => (
  <button
    onClick={() => {
      addToCart(id);
      openSidebar();
    }}
  >
    Add to cart
  </button>
);
```

This button component would generally be nested deep within the application
hierarchy. In the future, you might want to have this same functionality in a
completely separate component. This would be problematic since you now have two areas of your application performing the same logic.

You can see how this defeats the
<abbr title='Don&apos;t Repeat Yourself'>DRY</abbr> principle of software development. Instead of dispatching several actions from within a component, try
separating them into a thunk action.

Here I'll update the button to fire a single action, which also gives you the
benefit of one less property to pass down.

```javascript
// AddToCart.jsx
const AddToCart = ({ addItemToCart, id }) => (
  <button onClick={() => addItemToCart(id)}>Add to cart</button>
);
```

## Building a Thunk Action

```javascript
// actions.js
const addItemToCart = (id) => (dispatch, getState) => {
  dispatch(addToCart(id));
  dispatch(openSidebar());
};
```

Let's try to break this down. The thunk action `addItemToCart` is a function that accepts the cart ID as an argument and then returns another function. When
you dispatch any action, the Thunk middleware will check if the current action
type is a function and, if it is, call it and pass the Redux `dispatch` and `getState` as the arguments.

This is a pretty simple example, so let's complicate it a little. You
now need to save the item to the backend to allow the cart to persist between
sessions.

```javascript
// actions.js
const saveCartRequest = (id) => ({
  type: "SAVE_CART_REQUEST",
  payload: id,
});

const addItemToCart = (id) => (dispatch) => {
  dispatch(addToCart(id));
  dispatch(openSidebar());
  dispatch(saveCartRequest(id));
};
```

Did you know that you can trigger other thunk actions from within a thunk
action? You are not limited to only dispatching actions that have a return value.

Now instead of only passing the ID to `addToCart`, we'll pretend the reducer
requires the entire item to be sent instead. With these new requirements, you
can call `getState` to get the merchandise from the store and find the value you need to send.

```javascript
// actions.js
const addItemToCart = (id) => (dispatch, getState) => {
  const { merchandise } = getState();
  const item = merchandise.find((item) => item.id === id);
  dispatch(addToCart(item));
  dispatch(openSidebar());
  dispatch(saveCartRequest(id));
};

// Can't forget to update the `addToCart` action to use
// the `item` instead of the `id`
const addToCart = (item) => ({
  type: "ADD_TO_CART",
  payload: item,
});
```

## Testing a Thunk Action

Testing thunk actions is a little bit different from testing regular actions. The main difference is that we're no longer testing the returned value of an action, but whether `dispatch` is called with the correct values.

```javascript
// test.actions.js
const setup = () => {
  const cart = [];
  const merchandise = [
    {
      id: 5,
      type: "shirt",
    },
  ];
  const store = { merchandise, cart };
  const getState = () => store;
  return {
    dispatch: jest.fn(),
    getState,
  };
};

describe("actions", () => {
  describe("addItemToCart", () => {
    it("should dispatch all the correct actions", () => {
      const id = 5;
      // 1: Get the dispatch spy and mocked state
      const { dispatch, getState } = setup();

      // 2: Call the thunk action
      addItemToCart(id)(dispatch, getState);

      // 3: Test that the dispatch was called with the
      //    correct arguments
      expect(dispatch).toHaveBeenCalledTimes(3);
      expect(dispatch).toHaveBeenCalledWith({
        type: "ADD_TO_CART",
        payload: {
          id: 5,
          type: "shirt",
        },
      });
      expect(dispatch).toHaveBeenCalledWith({
        type: "OPEN_SIDEBAR",
      });
      expect(dispatch).toHaveBeenLastCalledWith({
        type: "SAVE_CART_REQUEST",
        payload: id,
      });
    });
  });
});
```

## Conclusion

What I love the most about this approach is that all the logic is contained
within a specific area of the application. It lives entirely outside your components, which helps keep them "dumb." Luckily, thunk actions are super simple to test,
which gives you no reason not to test them. Whether you use this approach now or later, it can handle your more challenging workflows.

## Versions

This article has been written and updated to support the following versions:

- **Redux:** 4.0.0
- **Redux Thunk:** 2.3.0
- **Jest:** 23.2.0

## Additional Resources

- [What is a thunk](https://daveceddia.com/what-is-a-thunk/)
- [Redux Thunks Dispatching Other Thunks — Discussion and Best Practices](https://medium.com/@talkol/redux-thunks-dispatching-other-thunks-discussion-and-best-practices-dd6c2b695ecf)

---

Source: https://devinschulz.com/blog/combining-synchronous-actions-using-redux-thunk/

