Prevent Race Conditions with Axios Requests
When web applications trigger rapid, asynchronous API calls—such as during search-as-you-type inputs, tab switching, or rapid filter toggling—network latency can cause responses to arrive out of order. This results in UI race conditions where older data overwrites the latest user action. This guide details how to resolve UI state race conditions using Axios through request cancellation, response validation tokens, and request debouncing.
1. Cancel Stale
Requests Using AbortController
The most effective way to prevent UI race conditions is to cancel
previous in-flight requests whenever a new request is triggered. Since
Axios v0.22.0, this is natively supported using the standard
AbortController API.
import axios from 'axios';
let abortController = null;
async function searchData(query) {
// Cancel previous pending request if it exists
if (abortController) {
abortController.abort();
}
// Instantiate a new controller for the current request
abortController = new AbortController();
try {
const response = await axios.get(`/api/search?q=${encodeURIComponent(query)}`, {
signal: abortController.signal
});
// Safely update UI state with latest response
updateUI(response.data);
} catch (error) {
if (axios.isCancel(error)) {
// Request was canceled; ignore and do not update UI
return;
}
// Handle legitimate errors
handleError(error);
}
}Canceling previous requests frees up network resources and guarantees that canceled responses will throw a cancellation error, safely bypassing your UI state updater.
2. Request Identifier and Timestamp Comparison
If canceling requests at the network layer is not feasible, you can maintain an incremental counter or timestamp in your component's state or scope. Responses are only applied to the UI if their identifier matches the most recent request.
let latestRequestId = 0;
async function fetchDetails(itemId) {
const currentRequestId = ++latestRequestId;
try {
const response = await axios.get(`/api/items/${itemId}`);
// Only update UI if this is still the most recent request
if (currentRequestId === latestRequestId) {
updateUI(response.data);
}
} catch (error) {
if (currentRequestId === latestRequestId) {
handleError(error);
}
}
}This approach allows all requests to complete over the wire but guarantees that obsolete responses are ignored before they can overwrite the active UI state.
3. Implement Debouncing on User Input
Preventing excessive network calls reduces the frequency of race conditions before they occur. Applying a debounce function ensures that an Axios request is only sent after the user has stopped triggering events for a specified duration.
import debounce from 'lodash.debounce';
const debouncedFetch = debounce((query) => {
searchData(query);
}, 300);
function onInputChange(event) {
debouncedFetch(event.target.value);
}Combining debouncing with AbortController ensures
optimal performance by minimizing request volume while strictly
enforcing sequential UI updates.