Most React devs have written this pattern at least once: component mounts → useEffect fires → fetch starts → data arrives → re-render It works... Until it doesn't. Here's what useEffect silently fails to handle for you, and why it matters. 🔴 No caching Every component mount sends a new request. Navigate away and come back? Another request — even if the data hasn't changed at all. 🔴 Boilerplate You need three useState calls just to track data, loading, and error. On every single fetch, in every single component. 🔴 Race conditions If the user navigates quickly, multiple fetches fire in parallel. The one that resolves last wins — even if it carries stale data. Good luck reproducing this one in dev. 🔴 Request waterfalls Parent fetches, renders children, children fetch. Each step waits for the previous one. The deeper the component tree, the slower the page. BUT ... React Query solves all four problems with a single hook. Look at the right side: same component, a fraction of the code. And out of the box you get: → Caching — same query, zero extra requests → Deduplication(multiple components, one fetch) → Background refetching on window focus → Race condition handling, automatically The key insight: useEffect is a synchronization tool. It was built to sync your component with external systems, not to serve as a data-fetching layer. It just happened to be the only option for a long time. React Query exists because data fetching deserves its own abstraction. #React #ReactQuery #WebDevelopment #Frontend #JavaScript
Using useEffect became the standard simply because it gets the job done. However, moving to libraries like React Query goes beyong just writing cleaner code - it fundamentally improves the overall logic and architecture of the app. Having built-in features for request management, like caching and revalidation, makes a huge diference because it stops us from reinveintg complex state management (loading, erros, race conditions) for every single network call.
Using useEffect for data fetching used to feel “normal.” But once the app grows, caching and race conditions start to hurt.
“Friends don’t let friends use useEffect”. useEffect nowaways is only needed to watch native browser updates, like viewport width.
Mate, checkout Granular. The new paradigm is designed to leave all of these nonsense react architecture issues behind. https://granular.web.app
I’ve seen very similar problems when data fetching logic starts spreading across multiple components. It works fine at first, but as the application grows you start seeing duplicated requests, race conditions, and increasingly complex state handling. Having a dedicated data layer makes a huge difference for scalability and maintainability.
The race condition point is the one that gets people the most because it almost never shows up in local dev. React Query basically made an entire category of bugs irrelevant. Took me longer than I'd like to admit to stop reaching for useEffect for data fetching out of habit.
One nuance though: React Query isn’t always the right answer. For highly real-time apps (WebSockets, subscriptions, collaborative tools), you still need a different architecture. The real skill isn’t replacing useEffect. It’s understanding data lifecycles. Curious — when do you still prefer manual fetching?
Good distinction. useEffect synchronizes state, it was never really designed to manage a data layer. Dedicated query abstractions solve a lot of subtle problems people only notice at scale.
useEffect is the enemy, and useQuery is the friend. It solved all my performance issues. 😅
One of the most useful libraries that I ever used. With react query you don't need to use useEffect hook any more. Thanks for sharing this post ♥️