Autoplay

    Redux: Avoiding Race Conditions with Thunks

    Dan AbramovDan Abramov

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

    reactReact
    ^15.0.0
    reduxRedux
    ^3.0.0
    Code

    Code

    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson
    Transcript

    Transcript

    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.

    Discuss

    Discuss