Skip to main content
React Query & Data Fetching

Module Overview

Estimated Time: 4 hours | Difficulty: Intermediate | Prerequisites: Networking, TypeScript
React Query (TanStack Query) solves a problem that every mobile developer eventually hits: server state is fundamentally different from client state, and trying to manage both with the same tool (Redux, Zustand, or Context) leads to a tangled mess of loading flags, stale data, and manual cache invalidation. Server state is data you do not own — it lives on a remote server, can be changed by other users at any time, and becomes stale the moment you fetch it. React Query treats this as a caching problem: it fetches data, caches it, keeps it fresh in the background, and gives you simple hooks to read the cache. This module covers caching strategies, background updates, optimistic updates, and building a robust data fetching layer. What You’ll Learn:
  • React Query setup and configuration
  • Query hooks and mutations
  • Caching strategies
  • Optimistic updates
  • Pagination and infinite queries
  • Error handling and retry logic

React Query Setup

Installation

Configuration

The two most important settings are staleTime (how long cached data is considered fresh) and cacheTime (how long unused data stays in memory). Setting these correctly has a direct impact on your app’s battery consumption and perceived performance — too low and you waste bandwidth on redundant requests, too high and users see outdated information.

Data Fetching Hooks

Basic Query

Component Usage


Mutations

Mutations are for operations that change data on the server — creating, updating, or deleting records. While queries are read-only and can be freely cached, retried, and deduplicated, mutations are write operations with side effects. React Query gives you a structured lifecycle (onMutate, onError, onSettled) that makes implementing optimistic updates and rollbacks straightforward.

Basic Mutation

Mutation with Loading State


Pagination

Infinite scrolling is the dominant pattern for mobile lists — users expect to scroll continuously and see new content loaded automatically, rather than tapping “Page 2” buttons. React Query’s useInfiniteQuery manages this seamlessly: it tracks page cursors, accumulates results across pages, and provides fetchNextPage / hasNextPage for your scroll handler.

Infinite Query

Paginated List Component


Advanced Patterns

Dependent Queries

Dependent queries let you chain data fetching — “fetch X, then use X’s result to fetch Y.” The enabled option is the key: when set to false, the query sits idle. When the dependency resolves and enabled becomes true, the query fires automatically.

Parallel Queries

Dependent Queries with useQueries


Cache Management

Prefetching

Manual Cache Updates


Error Handling

Global Error Handler

Query Retry Logic


Optimistic Updates

Optimistic updates are the secret to making your app feel instant. The idea: when a user performs an action (like toggling a status), immediately update the UI as if the server already confirmed it, then sync with the server in the background. If the server rejects the change, roll back to the previous state. This pattern is especially important on mobile where network latency is unpredictable. A 300ms delay that is barely noticeable on desktop WiFi feels sluggish on a cellular connection.

Complete Optimistic Update Pattern


Choosing the Right Data Fetching Strategy

Not every screen needs the same fetching approach. The decision depends on how frequently the data changes, how critical freshness is to the user experience, and how expensive the API call is. Decision framework:
  1. How often does this data change on the server? If the answer is “rarely,” set a long staleTime. If “constantly,” keep it short or use WebSocket subscriptions instead of polling.
  2. What happens if the user sees stale data? For a social feed, stale by 30 seconds is fine. For an account balance, stale by 30 seconds could cause a failed transaction.
  3. How expensive is the API call? If the endpoint is slow or rate-limited, cache aggressively and prefetch on navigation intent rather than on mount.
  4. Is the user on mobile data? This is the argument for longer staleTime defaults in React Native versus web apps. Every unnecessary refetch costs battery and cellular data.

React Query vs. Alternatives

A common question is when React Query is the right tool versus alternatives. Here is how they compare for mobile data fetching: When to use React Query: REST or GraphQL APIs, teams that want powerful caching without Redux boilerplate, apps where offline support and background refetching matter. When to consider alternatives: SWR if you want minimal bundle size and simpler API. Apollo Client if your backend is exclusively GraphQL and you need normalized caching. RTK Query if your app already uses Redux for client state.

Edge Cases and Gotchas

Race Conditions with Dependent Queries

When a parent query’s data changes (e.g., user switches accounts), dependent queries still hold cached data from the previous account. If you do not clear the cache on account switch, a user could briefly see another user’s data.

Stale Closures in Mutation Callbacks

A subtle bug: if your onSuccess callback references component state via a closure, it captures the state value at the time the mutation was initiated, not when it completes. For mutations that take several seconds, the state may have changed.

Infinite Query Memory Growth

useInfiniteQuery accumulates all fetched pages in memory. For feeds with hundreds of pages, this can consume significant memory on low-end devices. React Query v5 introduced maxPages to cap this:

Network Reconnection Thundering Herd

When a mobile device transitions from airplane mode or a tunnel back to connectivity, React Query’s refetchOnReconnect fires for every stale query simultaneously. On an app with 50+ query keys, this can overwhelm your API.

Best Practices

Set Proper Stale Times

Don’t refetch too often - configure staleTime based on data volatility

Use Optimistic Updates

Improve UX by updating UI before server responds

Handle Errors Gracefully

Implement proper error boundaries and retry logic

Invalidate After Mutations

Always invalidate queries after mutations that change data

Additional Practices for Production

  • Use query key factories. Instead of spreading string arrays across your codebase, centralize query keys in a factory object. This prevents typos, makes invalidation patterns discoverable, and keeps your code DRY:
  • Separate query hooks from components. Every useQuery call should live in its own custom hook file, not inline in a component. This makes queries reusable, testable in isolation, and keeps components focused on rendering.
  • Set retry: false for mutations. Queries are safe to retry because they are read-only. Mutations are not — retrying a POST /orders could create duplicate orders. Set retry: false or retry: 1 for mutations, and use idempotency keys on the server side.
  • Use placeholderData instead of separate loading states for detail screens. When navigating from a list to a detail screen, seed the detail query with the list item data so the screen renders instantly while the full data loads in the background:

Next Steps

Module 16: Local Storage & Databases

Learn to persist data locally with AsyncStorage and SQLite