Redux Thunks Dispatching Other Thunks — Discussion and Best Practices

This discussion is a detour I wanted to take in the midst of writing “Redux Testing Step by Step: A Simple Methodology for Testing Business Logic”.


Consider the following use-case — we want to show a list of posts fetched from a server. The list contains both published and unpublished posts and can show them independently. This is the implementation:

Our UI occasionally needs to dispatch an action to fetch published posts, occasionally needs to dispatch fetch of unpublished posts and occasionally needs to dispatch fetch of all posts. This means we have 3 separate exported actions — 3 thunks. In order to promote code reuse, we’ve decided to implement the third by dispatching the first two internally.

Unit testing the third thunk — fetchAllPosts

The spec for the fetchAllPosts action is to set the state to loading, call the services to fetch the data, store the fetched data in the state and stop loading. This means we want our unit test to assert all of the above.

Due to the limitations in redux-testkit, these assertions are not possible to make and we have no easy way to unit test what happens when the internal thunks are dispatched. Since different thunks are always considered different units, the only place we can test for integration between thunks and execute all three in a single test is in the integration test for the entire store.

What are we doing wrong?

It seems that a unit test for fetchAllPosts should be able to assert that the correct changes in state took place. It’s part of its spec after all. So why isn’t this straightforward?

The problem is that the above implementation isn’t treating different thunks as different units. When dispatching another thunk, think of broadcasting an event. You’re not supposed to have intimate knowledge of other handlers of this event. In other words, you shouldn’t rely on the specifics of what dispatched thunks are doing. Particularly, you’re not supposed to rely on them to make the state modifications you are required to make by your own spec.

Our inability to test uncovered a code smell

When testing is difficult, it is usually an indication that our code is structured incorrectly and should be refactored. Let’s change the implementation, maintain code reuse but treat the different thunks as different units:

We’ve extracted the shared logic into regular functions called _fetchPostsHelper and _loadingHelper. As you can see, these functions are neither thunks nor actions — they’re just regular functions. Our three thunks are now completely independent. They’re different units.

Unit testing the third thunk — fetchAllPosts — revisited

Writing our unit test is now straightforward. Since we’re not dispatching a thunk from within a thunk, the limitations don’t apply.

Does this mean that we should never dispatch a thunk from within another thunk? No. There are plenty of use-cases where this is a good practice.

A thunk within a thunk

Consider the following use-case. Whenever the user fetches all posts, we also want to refresh our authentication token from the server. It makes sense to implement the token refresh logic as its own thunk — refreshToken. Does the fetchAllPosts thunk require intimate knowledge of the refreshToken thunk?

The answer is probably no. Besides triggering this event, fetchAllPosts does not rely on the specifics of what refreshToken is doing. Particularly, where the token is stored in state is not part of its spec. These thunks are different units, which yields the following implementation:

Summary

When you have a thunk dispatching another thunk stop and think whether the first has intimate knowledge of the inner workings of the second. If this is the case, refactor the dispatch to a regular function call. If this is not the case, dispatch away.

Show your support

Clapping shows how much you appreciated Tal Kol’s story.