How to ignore previous AJAX responses when using React Hooks
Tags: webdev, ajax, fetch, raceconditions
Published: 2021-03-15 09:58:29

If the user is searching something on the webpage and change the keyword and search again, this is usually not a problem, as the duration between them is usually quite long. But due to the network and server responding late, it can still be a problem.

It is a more common problem if the user clicks between two options on the page really fast, such as the color red, or 16GB, and then choose the color blue, or 32GB, but the first AJAX response comes back after the second one. In this case, the user may have the UI selecting the red or the 16GB option, while the data on the page shows blue or 32GB.

One more common situation is: if the user clicks on "Next" to fetch the next record, or the next page of data, and the user clicks it quite fast, like a few times in a row, then the order of the response can cause some issue. For example, if the user clicks it 3 times, and the second request comes back just a split second after the third one, and so the page can show the results of the second page, while the current page number showing on screen is Page 3.

There are different ways to handle this problem.  But if we just want to not honor the previous requests, we can use closure to handle this:

```js
useEffect(() => {
  let honorRequest = true;
  
  fetch(" ... ")
    .then(res => res.json())
    .then(d => { if (honorRequest) setData(d) });

  return () => { honorRequest = false; };

}, [color]);
```

So if `color` changes, the `useEffect()` would run the function `() => { honorRequest = false; }` and since it is a closure that can access `honorRequest`, would set it to false and not set the data to be displayed on the page.

Demo of the bug: https://codesandbox.io/s/immutable-fog-i9gd8?file=/src/App.js

The fetch for user 2 (Peter) has a built in delay of 3 seconds, and we can see the bug if we click on Peter, and within 3 seconds, we click Michael.  Then a few second later, the UI (the radio button) has Michael selected, but the content says Peter.

When we have the mechanism to ignore the previous AJAX responses, then we don't have that issue any more. Demo: https://codesandbox.io/s/modest-wiles-j2u4g?file=/src/App.js

If for some reason, we use a fetch function without using `useEffect()` and dependencies, and just want the earlier fetches to be ignored, we can use:

```js
const fetchID = useRef(0);

function fetchData() {
  const thisFetchID = ++fetchID.current;
  
  fetch(" ... ")
    .then(res => res.json())
    .then(d => { if (thisFetchID === fetchID.current) setData(d) });
}
```
This way, we keep a local state, so that if there is any newer fetch, `fetchID.current` would have incremented, and cause the earlier `then` handler not to call `setData()`.

Demo of the issue: https://codesandbox.io/s/withered-butterfly-8h8k3?file=/src/App.js

Demo of the previous response ignored: https://codesandbox.io/s/bold-feather-c0p7y?file=/src/App.js

Another way to handle this is to directly cancel the data fetch: [Axios cancellation](https://github.com/axios/axios#cancellation), [Stackoverflow post](https://stackoverflow.com/questions/38329209/how-to-cancel-abort-ajax-request-in-axios/64088389), [fetch cancellation](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort), and [JavaScript Tutorial](https://javascript.info/fetch-abort).