Using AbortController to deal with race conditions in React

JavaScript React

When developing applications, data fetching is one of the most fundamental tasks. Despite that, there are some things to watch out for: one of them is race conditions. This article explains what they are and provides a solution using the AbortController.

Identifying a race condition

A “race condition” is when our application depends on a sequence of events, but their order is uncontrollable. For example, this might occur with asynchronous code.

The term “race condition” dates to as far as 1954 and was first used in the field of electronics. A popular example of a race condition can be present in multithreading when multiple threads attempt to change shared data and race to access it first.

To visualize this, let’s use React and React router.

App.tsx

Above, we define the route and render the component.

Post.tsx

In the component, we either display a loading indicator or the fetched data. The most important part takes place in the hook.

usePostLoading.tsx

If you want to know more about the hook, check out Understanding the useEffect hook in React. Designing custom hooks

Above, we fetch a post based on the URL. So, if the user visits , we send a GET request to .

Defining the race condition

The above approach is very common, but there is a catch. Let’s consider the following situation:

  • The user opens to see the first post,
    • we start fetching the post with id ,
    • there are some Internet connection issues,
    • the post does not load yet,
  • Not waiting for the first post, the user changes the page to
    • we start fetching the post with id ,
    • the post loads without issues and is available almost immediately,
  • The first post finishes loading,
    • the line executes, overwriting the current state with the first post,
    • even though the user switched the page to , the first post is still visible.

Unfortunately, we can’t cancel a promise once we create it. There was a proposal to implement a feature like that, but it has been withdrawn.

The most straightforward fix for the above issue is introducing a variable, as suggested by Dan Abramov. When doing that, we need to use the fact that we can clean up after our hook. React will call it when our component unmounts if  returns a function.

usePostLoading.tsx

Now, the bug we’ve seen before is no longer appearing:

  • The user opens to see the first post,
    • we start fetching the post with id ,
  • Not waiting for the first post, the user changes the page to ,
    • the cleans after the previous post and sets to true,
    • we start fetching the post with id ,
    • the post loads without issues and is available almost immediately,
  • The first post finishes loading,
    • the line does not execute because of the variable.

Now, if the user changes the route from to , we set to . Thanks to that, if the promise resolves when we no longer need it, the is not called.

Introducing AbortController

While the above solution fixes the problem, it is not optimal. The browser still waits for the HTTP request to finish but ignores its result. To improve this, we can use the AbortController.

With it, we can abort one or more fetch requests. To do this, we need to create an instance of the AbortController and use it when making the fetch request.

Above, we pass the through the fetch options. Thanks to that, the browser can stop the request when we call .

We can pass the same to multiple fetch requests. If we do that, aborts multiple requests.

The above also fixes the issue we’ve had with the race conditions.

  • The user opens to see the first post,
    • we start fetching the post with id ,
  • Not waiting for the first post, the user changes the page to ,
    • the cleans after the previous post and runs ,
    • we start fetching the post with id ,
    • the post loads without issues and is available almost immediately,
  • The first post never finishes loading because we’ve already aborted the first request.

We can observe the above behavior in the network tab in the developer tools.

A crucial thing about calling the is that it causes the fetch promise to be rejected. It might result in an uncaught error.

To avoid the above message, let’s catch the error.

Aborting other promises

Besides using the AbortController to cancel fetch requests, we can also use it in our functions. Let’s create a function that returns a promise to see this.

We can modify our function to accept a property similar to the fetch function. To do this, we need to use the fact that the emits an event when we call .

Now, we need to pass the property to  and abort the promise.

Summary

In this article, we’ve gone through the problem of race conditions in React. To solve it, we’ve learned the idea behind the AbortController and expanded on the solution provided by Dan Abramov. Besides that, we’ve also learned how we can use the Abort Controller for other purposes. It required us to dig deeper and understand better how the AbortController works. Thanks to all of the above, we now know how to use the AbortController in various situations.

Subscribe
Notify of
guest
3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Tung
Tung
2 years ago

Thank you for your article! Today I have learned a new thing – AbortController.

Mostafa
Mostafa
1 year ago

Thanks for this incredible article

Rahul
Rahul
1 year ago

Thank you but can we do abort in onChange funtion?