Can’t You Just Call an API Directly in mounted? What Are Race Conditions and Optimistic Updates?
Making an API call → getting data and processing it → rendering on the page: this workflow is the most familiar to all frontend developers.
function getBooks() {
return http.get('/api/book')
}
Here, http could be axios or the browser’s native fetch—that’s fine. But personally, I believe you shouldn’t assign the getBooks function directly to a button’s click event or write it directly inside mounted / useEffect.
Let’s discuss why.
1. Missing Derived State
Every API call means you’ll have to handle a lot of derived states: is the request waiting for a response? Did it succeed or fail? What’s the error message?
These derived states are useful, but if you have to manually handle them for every API call, you might find it too much trouble—there are too many variables (isLoading, isPending, isError, error, data...). So you may tend to ignore these derived states most of the time and only write handling logic when you have to. But I think that’s a bit too crude and sets you up for maintenance headaches in the future.
For example, if you don’t add isLoading or isPending, the UI will freeze while waiting for the API response, giving users a terrible experience. Without isError or error, if the backend API crashes, you’ll be blamed for letting the page break without error handling.
Of course, a rough approach is to wrap interceptors so that every loading state and error gets handled uniformly. But if you have high expectations for user experience, you’ll find that “uniform handling” can’t satisfy every specific scenario. A uniform big loading spinner is the crudest approach. An infinite list’s “load more” button is often more refined and intuitive. Some scenarios require skeleton screens, progress bars, and so on. So ignoring derived states or handling them all the same way is not a reasonable solution.
2. Race Conditions
Let me first explain what a race condition is.
Here’s a somewhat abstract example: You go to a hair salon. You tell hairdresser A you want big waves, and A agrees and goes to prepare tools. Then you tell hairdresser B you want a shaved head, and B goes to prepare tools too. Then a wave of sleepiness hits you and you fall asleep. Now when you wake up, what hairstyle will you have? Nobody knows—the answer depends on which hairdresser prepares the tools and starts working first. Even though you expressed your request to A first, that doesn’t guarantee A will act first. That’s a race condition: the order in which requests are made has nothing to do with the order in which they are fulfilled, but when they get out of sync, users get confused.
Now think about development scenarios like search, filtering, pagination—the faster the user interacts, the higher the chance of rendering incorrect data. Because of network latency, if the user’s interaction frequency exceeds the network response speed, the rendered result will be vastly different from what’s expected.
3. Data Caching
Another overlooked issue is data caching. Imagine a user goes from a list page to a detail page, then returns to the list page. If you haven’t carefully handled caching logic, your code will make the list API request again. This wastes network resources and forces the user to wait for data to load again.
If you’re experienced with Vue, you might know you can use keep-alive to cache page state and avoid re‑fetching data on soft navigations, or use hooks like onActivated to keep cached pages updated. But knowing when to update a cache is “the hardest problem in computer science.” Some data may never need to be actively updated—for example, your own nickname. As long as you cache it after each change, you never have to request the server again. Another example: “daily hot topics” or “trending searches”—refreshing them once an hour is perfectly acceptable.
Also, for data that isn’t time‑sensitive, even if the product manager insists on fetching the latest data on every navigation, you don’t need to block the UI with a loading state until the server responds. You can show cached data first and make a silent request, then compare the new data with the cached data. If there’s no difference, do nothing; if there is, update the UI. This strikes a balance between user experience and data freshness.
Solution
At this point, you might think: “Is this author over‑engineering? Just write a fetch inside onMounted and be done with it—why make it so complicated?”
But it’s not me trying to be extra—there are solutions for all of these problems. The community already has many mature libraries to handle them.
If you’re a React user, you probably know what I’m about to say. The official React docs have clearly pointed out the drawbacks of making network requests directly inside useEffect and provided a solution.
For example, the code below already exposes derived states: data, isLoading, error. It also cleanly handles loading and error logic. More importantly, it caches data by default, minimizes UI blocking to improve user experience, and only re‑renders components when the server‑returned data actually changes.
import { useQuery } from '@tanstack/react-query'
function useFetchBooks({ userId, page }) {
const { data, isLoading, error } = useQuery({
queryKey: ['book', page],
queryFn: () => fetchBook(page),
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>{data}</div>
}
Actually, the intricacies of network requests go far beyond what I’ve mentioned. Other topics I can think of include optimistic updates, retry mechanisms, debouncing/throttling, request deduplication, offline handling, and more.
For example, optimistic update assumes an operation will succeed and immediately updates the UI. If the request fails, it rolls back and informs the user—so the user’s feedback feels smooth and fast. This is what wechat likes, Bilibili comments, and many other features do.
I hope this article helps you. If you found it useful, feel free to give the author a “like, subscribe, and share” (one‑click triple support). I currently publish technical sharing every week. Follow me to become a senior developer sooner.
If anything is unclear or you have other caveats about making requests, feel free to leave a comment for discussion. I read every comment. Thanks, everyone!