Redux: Avoiding Race Conditions with Thunks

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

We will learn how Redux Thunk middleware lets us conditionally dispatch actions to avoid unnecessary network requests and potential race conditions.

[00:00] I'm increasing the delay in my fake API client to five seconds. This lets me notice a problem. We don't check if the tab is already loading before starting a request, and then a bunch of receive todos action comes back, potentially resulting in a race condition.

[00:18] To fix this, I can exit early from the fetch todos action creator if I know that I'm already fetching the todos for the given filter. I will use the existing top level get is fetching selector, that accepts the store state and the filter as arguments. If it returns true, I will exit early from my thunk without dispatching any actions.

[00:43] The get is fetching selector is defined inside the top level reducer file. I will import it as a named import from reducers. Another function I'm using that isn't defined in this file is get state, and it belongs to the store object, but I don't have access, truly, directly from the action creator.

[01:05] However, I can make it so that the thunk middleware injects not just store dispatch function inside the thunk actions, but also store get state function. This way, I can grab it as a second argument after dispatch inside my thunk action creator, so I'm adding get state as a second argument.

[01:28] The fetch todos action creator now dispatches actions conditionally, and if I run the app, I can't get it to produce more than three concurrent requests.

[01:39] Only after the corresponding receive todos actions come back, the is fetching flag gets reset, and we can request the new todos. This is a good way to avoid unnecessary network operations and potential race conditions.

[01:55] This is a very common pattern, so you don't have to write the thunk middleware yourself. Instead, you can open up a terminal and run npm install save redux-thunk.

[02:06] It installs the thunk middleware that is very similar to the one I wrote here, so I can remove my version of thunk middleware, and instead, I can import thunk from redux-thunk.

[02:21] Finally, let's take a look at the return value of the thunk. It returns a promise. It doesn't have to, but it's convenient for the calling code, so I will change the early return to also return a promise that resolves immediately.

[02:35] The thunk middleware itself does not use this promise, but it becomes the return value of dispatching this action creator, so I can use it inside the component to schedule some code after the asynchronous action has completed.

[02:53] Let's recap how we use redux-thunk to dispatch actions asynchronously and conditionally. In the component, we dispatch the fetch todos action, which is implemented as an asynchronous action creator that returns a thunk that is a function that will get interpreted by the redux-thunk middleware.

[03:16] I import thunk from the redux-thunk package I installed from npm, and I added thunk to the list of redux middlewares I use to create my store. The thunk middleware sees that I dispatched a function rather than an action, so it calls it with dispatch as an argument so that it can dispatch multiple times.

[03:38] It also passes a second argument called get state that lets me get the current state of the redux store. I pass the state to the get is fetching selector that I import as a top level named import from the reducers file.

[03:55] I am passing the current state of the store and the filter to get is fetching, and if I am already fetching the todos for this filter, then I will exit early from the thunk so that I don't call the API or dispatch any actions.

[04:13] Finally, the thunk middleware has no opinion on what you return from the thunk itself. As a convention, I prefer to always return a promise that represents the corresponding asynchronous operation, whether or not it has called the server.

[04:28] The return value of the thunk becomes the return value of dispatching this thunk, and I can use this to wait for the asynchronous operation to finish inside my component in order to show a message or start an animation.