DEV Community

Cover image for Inside React Query: What’s Really Going On
Mohamad Msalme
Mohamad Msalme

Posted on

Inside React Query: What’s Really Going On

Know WHY — Let AI Handle the HOW 🤖

React Query isn’t just hooks. It’s a data orchestration engine. Treat it like hooks, and you’ll end up with duplicate fetches, stale state, and components re-rendering like they’re on espresso.

Let’s go step by step inside React Query, show what each piece does, and why understanding it will make you a much stronger React developer.

QueryClient — The Conductor of Your Data Orchestra

display query client

1- The QueryClient isn't just a prop you pass to
QueryClientProvider
.

It's the conductor of your entire data-fetching orchestra — the single source of truth that coordinates all your queries, manages the cache, and ensures everything stays in sync.

2- Why QueryClient Must Be Stable
Here's the critical insight: QueryClient holds the QueryCache instance. When you create multiple QueryClient instances, you're creating multiple, isolated caches. This breaks React Query's fundamental promise of shared state management.

// ❌ WRONG: Creates new client on every render
function App() {
// New instance every render! mean new cashe instance 
  const queryClient = new QueryClient() 

  return (
    <QueryClientProvider client={queryClient}>
      <RestOfYourApp />
    </QueryClientProvider>
  )
}

// ✅ CORRECT: Stable reference outside component
const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RestOfYourApp />
    </QueryClientProvider>
  )
}

// ✅ ALSO CORRECT: Stable reference with useState
function App() {
  const [queryClient] = useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      <RestOfYourApp />
    </QueryClientProvider>
  )
}`
Enter fullscreen mode Exit fullscreen mode

QueryCache — Your In-Memory Data Warehouse

display QueryCache

The QueryCache is React Query's in-memory data warehouse — a JavaScript object where the keys are stable serialized versions of your queryKey (called queryKeyHash) and the values are Query class instances containing your actual data.

// it is just to simplified the idea 

// The Simple Truth: It's Just an Object
// Simplified QueryCache structure
const queryCache = {
  // queryKeyHash: Query instance
  '["users",1]': QueryInstance1,
  '["posts","published"]': QueryInstance2,
  '["user",1,"profile"]': QueryInstance3,
}

// Where each Query instance contains:
class Query {
  constructor() {
    this.data = undefined        // Your actual data
    this.error = undefined       // Any errors
    this.status = 'idle'         // idle, loading, success, error
    this.dataUpdatedAt = Date.now()
    this.errorUpdatedAt = undefined
    this.staleTime = 0
    // ... more metadata
  }
}
Enter fullscreen mode Exit fullscreen mode

Query — The Smart Data Manager with Selective Observers

display Query

The Query class is where all the magic happens in React Query. It's not just a data container — it's a sophisticated state machine that manages fetching, retries, cancellation, deduplication, and most importantly, knows exactly who's watching and what they care about.

The Core Insight: Selective Observer Notifications
Here's the critical concept you identified: Query knows who's subscribed and what each subscriber cares about. This enables React Query to be incredibly efficient by only notifying observers about changes they actually care about.

// Component A: Only cares about loading state
function LoadingIndicator() {
  const { isLoading } = useQuery({
    queryKey: ['user', 1],
    queryFn: fetchUser
  })

  // This component ONLY gets notified when isLoading changes
  // It doesn't care about data, error, or any other state
}

// Component B: Only cares about error state  
function ErrorBoundary() {
  const { isError } = useQuery({
    queryKey: ['user', 1],
    queryFn: fetchUser
  })

  // This component ONLY gets notified when isError changes
  // It doesn't care about loading, data, or any other state
}

// Component C: Only cares about data
function UserProfile() {
  const { data } = useQuery({
    queryKey: ['user', 1],
    queryFn: fetchUser
  })

  // This component ONLY gets notified when data changes
  // It doesn't care about loading, error, or any other state
}
Enter fullscreen mode Exit fullscreen mode

QueryObserver — The Smart Bridge Between Components and QueryCache

display QueryObserver

The QueryObserver is the intelligent bridge that connects your React components to the QueryCache. Think of it as a smart subscription service that knows exactly what your component cares about and only notifies it when those specific things change.

The Core Concept: Smart Subscription
Your insight is spot-on: useQuery is essentially a QueryObserver that creates a subscription between your component and the QueryCache. Here's how it works:

// What you write
const { data, isLoading } = useQuery({
  queryKey: ['user', 1],
  queryFn: () => fetchUser(1)
})

// What actually happens internally
const observer = new QueryObserver(queryClient, {
  queryKey: ['user', 1],
  queryFn: () => fetchUser(1)
})

// Observer creates subscription to QueryCache
observer.subscribe(componentCallback)
Enter fullscreen mode Exit fullscreen mode

🧠 The Bigger Picture

display The Bigger Picture

This explanation transforms React Query from a "magic hook library" into a sophisticated data orchestration system. The key insight is that React Query isn't just about making API calls easier—it's about creating a predictable, performant, and scalable data layer for React applications.
The architectural understanding (QueryClient → QueryCache → Query → QueryObserver) gives you the mental model you need to:
1- Debug React Query issues effectively
2- Optimize performance by understanding re-render patterns
3- Design better data fetching strategies
4- Avoid common pitfalls like unstable QueryClient instances

💭 The Takeaway

Learn the HOW: "Use React Query hooks to fetch and cache data."
When you understand the WHY: "QueryClient orchestrates shared state, QueryCache eliminates duplicate requests, and QueryObserver enables selective re-renders," you gain a powerful data orchestration system that makes your React applications more performant and predictable.

Top comments (0)