Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT NachrichtenTelekom: Neuer Reise-eSIM-Dienst T-Travel startet weltweit(21.09.2026 um 11:45 Uhr)
IT NachrichtenSamsung Wallet: Neue Banken mit an Bord(21.09.2026 um 13:07 Uhr)
IT NachrichtenTelekom: Neuer Reise-eSIM-Dienst T-Travel startet weltweit(21.09.2026 um 11:45 Uhr)
IT NachrichtenSamsung Wallet: Neue Banken mit an Bord(21.09.2026 um 13:07 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

React Query Architecture — Complete Flow from Hook to Render

Ever wondered what actually happens when you call useQuery? How does React Query: ✅ Connect your component to the cache? ✅ Know when to re-render? ✅ Avoid unnecessary renders? ✅ Work with React 18's concurrent features? This guide show…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Ever wondered what actually happens when you call useQuery?



How does React Query:




  • ✅ Connect your component to the cache?

  • ✅ Know when to re-render?

  • ✅ Avoid unnecessary renders?

  • ✅ Work with React 18's concurrent features?



This guide shows you the complete internal flow from hook call to screen update.



From @tanstack/react-query









📋 Table of Contents




  • The Big Picture

  • Complete Architecture Diagram

  • Layer-by-Layer Breakdown

  • The Complete Flow

  • React Integration

  • QueryObserver Deep Dive

  • useSyncExternalStore Explained

  • Re-render Logic

  • Real Code Walkthrough

  • Performance Optimizations

  • Mental Models









⚡ Quick Summary




TL;DR: React Query uses:




  • QueryObserver as the bridge between React and cache

  • useSyncExternalStore to subscribe to external state

  • Structural sharing to prevent unnecessary renders

  • Observer pattern for fine-grained updates

  • Batched notifications via NotifyManager



Length: ~35 min read | Level: Advanced







🎯 The Big Picture {#the-big-picture}






Simple Mental Model:






React Component

useQuery (Hook)

QueryObserver (Bridge)

Query (State Machine)

QueryCache (Database)

QueryClient (Orchestrator)









Key Insight:




React Query is NOT React state.


It's an external store that React observes via useSyncExternalStore.










🏗 Complete Architecture Diagram {#complete-architecture-diagram}






Visual Overview:






┌─────────────────────────────────────────────────────────────┐
│ React Component │
│ const { data, isLoading } = useQuery({ queryKey, queryFn })│
└────────────────────┬────────────────────────────────────────┘

│ (1) Hook call

┌─────────────────────────────────────────────────────────────┐
│ useQuery Hook │
│ - Creates/reuses QueryObserver │
│ - Calls useSyncExternalStore │
│ - Returns result to component │
└────────────────────┬────────────────────────────────────────┘

│ (2) Create observer

┌─────────────────────────────────────────────────────────────┐
│ QueryObserver │
│ Role: Bridge between React and Query │
│ - Subscribes to Query │
│ - Tracks accessed properties │
│ - Diffs state changes │
│ - Notifies React when needed │
└────────────────────┬────────────────────────────────────────┘

│ (3) Subscribe to Query

┌─────────────────────────────────────────────────────────────┐
│ Query │
│ Role: Individual query state machine │
│ - Manages state (data, error, status) │
│ - Handles fetch logic via Retryer │
│ - Notifies observers on state change │
│ - Stored in QueryCache │
└────────────────────┬────────────────────────────────────────┘

│ (4) Stored in

┌─────────────────────────────────────────────────────────────┐
│ QueryCache │
│ Role: Database of all Query instances │
│ - Map<queryHash, Query> │
│ - Fast O(1) lookup │
│ - Garbage collection │
│ - Global event emitter │
└────────────────────┬────────────────────────────────────────┘

│ (5) Accessed via

┌─────────────────────────────────────────────────────────────┐
│ QueryClient │
│ Role: Central orchestrator │
│ - API for interacting with cache │
│ - Invalidation, refetch, mutations │
│ - Configuration management │
└─────────────────────────────────────────────────────────────┘












📊 Layer-by-Layer Breakdown {#layer-by-layer-breakdown}






Layer 1: React Component 🎨



Your code:




function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})

if (isLoading) return <Spinner />
if (error) return <Error />
return <List data={data} />
}






What component does:




  • Calls useQuery hook

  • Receives reactive result

  • Re-renders when result changes









Layer 2: useQuery Hook 🪝



Internal implementation (simplified):




function useQuery(options) {
const queryClient = useQueryClient()

// Create observer (memoized)
const observer = React.useMemo(
() => new QueryObserver(queryClient, options),
[]
)

// Update options on every render
observer.setOptions(options)

// Subscribe using React 18's useSyncExternalStore
const result = useSyncExternalStore(
// Subscribe function
(onStoreChange) => observer.subscribe(onStoreChange),

// Get current snapshot
() => observer.getCurrentResult(),

// Get server snapshot (for SSR)
() => observer.getCurrentResult()
)

return result
}






Key responsibilities:




  1. Create/reuse QueryObserver

  2. Subscribe to observer via useSyncExternalStore

  3. Return current result









Layer 3: QueryObserver 👀



Internal structure:




class QueryObserver {
constructor(client, options) {
this.client = client
this.options = options
this.listeners = new Set()
this.currentQuery = undefined
this.currentResult = undefined
this.trackedProps = new Set()
}

// Called by useSyncExternalStore
subscribe(listener) {
this.listeners.add(listener)

// Get or create query
this.updateQuery()

// Subscribe to query
const unsubscribe = this.currentQuery.subscribe(this)

return () => {
this.listeners.delete(listener)
unsubscribe()
}
}

// Get current result for component
getCurrentResult() {
return this.currentResult
}

// Called when query updates
onQueryUpdate() {
// Create new result
const result = this.createResult()

// Check if changed
if (this.hasResultChanged(result)) {
this.currentResult = result

// Notify React
this.listeners.forEach(listener => listener())
}
}

// Track which properties component accessed
createResult() {
const query = this.currentQuery
const state = query.state

return {
data: state.data,
error: state.error,
isLoading: state.status === 'pending',
isSuccess: state.status === 'success',
isError: state.status === 'error',
// ... more properties
}
}

// Smart diffing
hasResultChanged(result) {
// If no tracked props, check all
if (this.trackedProps.size === 0) {
return !shallowEqual(this.currentResult, result)
}

// Only check tracked props
for (const key of this.trackedProps) {
if (this.currentResult[key] !== result[key]) {
return true
}
}

return false
}
}






Key responsibilities:




  1. Bridge between React and Query

  2. Track which properties component uses

  3. Diff state changes efficiently

  4. Notify React only when needed









Layer 4: Query 🔧



Internal structure:




class Query {
constructor(config) {
this.queryKey = config.queryKey
this.queryHash = hashQueryKey(config.queryKey)
this.options = config.options

// State
this.state = {
data: undefined,
dataUpdatedAt: 0,
error: null,
errorUpdatedAt: 0,
fetchStatus: 'idle',
status: 'pending'
}

// Observers (components watching this query)
this.observers = new Set()

// Current fetch promise
this.promise = null

// Retry controller
this.retryer = null

// GC timer
this.gcTimeout = null
}

// Add observer (component)
subscribe(observer) {
this.observers.add(observer)
this.clearGcTimeout()

// Notify observer of current state
observer.onQueryUpdate()

return () => {
this.observers.delete(observer)

// No observers? Schedule GC
if (this.observers.size === 0) {
this.scheduleGc()
}
}
}

// Fetch data
async fetch() {
// Already fetching?
if (this.promise) {
return this.promise
}

// Update state
this.setState({
fetchStatus: 'fetching'
})

// Create retryer
this.retryer = new Retryer({
fn: this.options.queryFn,
retry: this.options.retry,
retryDelay: this.options.retryDelay
})

// Start fetch
this.promise = this.retryer.start()
.then(data => {
this.setState({
data,
dataUpdatedAt: Date.now(),
status: 'success',
fetchStatus: 'idle',
error: null
})
return data
})
.catch(error => {
this.setState({
error,
errorUpdatedAt: Date.now(),
status: 'error',
fetchStatus: 'idle'
})
throw error
})
.finally(() => {
this.promise = null
})

return this.promise
}

// Update state and notify observers
setState(updater) {
this.state = { ...this.state, ...updater }

// Notify all observers
this.notify()
}

// Notify observers
notify() {
notifyManager.batch(() => {
this.observers.forEach(observer => {
observer.onQueryUpdate()
})
})
}

// Schedule garbage collection
scheduleGc() {
this.clearGcTimeout()
this.gcTimeout = setTimeout(() => {
this.cache.remove(this)
}, this.options.gcTime)
}

clearGcTimeout() {
if (this.gcTimeout) {
clearTimeout(this.gcTimeout)
this.gcTimeout = null
}
}
}






Key responsibilities:




  1. Store query state

  2. Handle fetch lifecycle

  3. Manage observers

  4. Notify on state changes









Layer 5: QueryCache 🗄️



Internal structure:




class QueryCache {
constructor(config) {
this.config = config
this.queries = []
this.queriesMap = new Map()
}

// Build or get query
build(client, options) {
const queryKey = options.queryKey
const queryHash = hashQueryKey(queryKey)

// Existing query?
let query = this.queriesMap.get(queryHash)

// Create new if needed
if (!query) {
query = new Query({
cache: this,
queryKey,
queryHash,
options,
state: options.initialData
? {
data: options.initialData,
status: 'success',
dataUpdatedAt: Date.now()
}
: undefined
})

this.queries.push(query)
this.queriesMap.set(queryHash, query)
this.notify({ type: 'added', query })
}

return query
}

// Find query by key
find(queryKey) {
const queryHash = hashQueryKey(queryKey)
return this.queriesMap.get(queryHash)
}

// Find all matching queries
findAll(filters) {
return this.queries.filter(query =>
matchQuery(filters, query)
)
}

// Remove query
remove(query) {
const index = this.queries.indexOf(query)
if (index !== -1) {
this.queries.splice(index, 1)
this.queriesMap.delete(query.queryHash)
this.notify({ type: 'removed', query })
}
}

// Notify listeners (e.g., DevTools)
notify(event) {
this.listeners.forEach(listener => {
listener(event)
})
}
}






Key responsibilities:




  1. Store all Query instances

  2. Fast lookup by hash

  3. Garbage collection

  4. Event emission









Layer 6: QueryClient 🧠



We covered this in detail in the QueryClient guide!









🔄 The Complete Flow {#the-complete-flow}






Step-by-Step Execution:






┌─────────────────────────────────────────────────────────────┐
│ Step 1: Component Mounts │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ function UserList() { │
│ const result = useQuery({ │
│ queryKey: ['users'], │
│ queryFn: fetchUsers │
│ }) │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 2: useQuery Hook Executes │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ // Inside useQuery │
│ const observer = useMemo( │
│ () => new QueryObserver(client, options), │
│ [] │
│ ) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 3: QueryObserver Created │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ QueryObserver { │
│ client: QueryClient │
│ options: { queryKey, queryFn } │
│ listeners: Set() │
│ currentQuery: undefined │
│ currentResult: undefined │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 4: useSyncExternalStore Called │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ useSyncExternalStore( │
│ // Subscribe callback │
│ (onStoreChange) => { │
│ return observer.subscribe(onStoreChange) │
│ }, │
│ // Get snapshot │
│ () => observer.getCurrentResult() │
│ ) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 5: Observer Subscribes to Query │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ observer.subscribe(onStoreChange) { │
│ // Get or create query │
│ const query = queryCache.build(options) │
│ │
│ // Subscribe to query │
│ const unsubscribe = query.subscribe(this) │
│ │
│ // Store listener │
│ this.listeners.add(onStoreChange) │
│ │
│ return () => { │
│ unsubscribe() │
│ this.listeners.delete(onStoreChange) │
│ } │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 6: Query Built/Retrieved from Cache │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ queryCache.build(options) { │
│ const queryHash = hash(['users']) │
│ │
│ // Check if exists │
│ let query = this.queriesMap.get(queryHash) │
│ │
│ // Create if new │
│ if (!query) { │
│ query = new Query({ │
│ queryKey: ['users'], │
│ queryHash: '["users"]', │
│ options │
│ }) │
│ this.queriesMap.set(queryHash, query) │
│ } │
│ │
│ return query │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 7: Query Adds Observer │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ query.subscribe(observer) { │
│ // Add to observers set │
│ this.observers.add(observer) │
│ │
│ // Cancel GC │
│ this.clearGcTimeout() │
│ │
│ // Notify observer immediately │
│ observer.onQueryUpdate() │
│ │
│ return unsubscribe function │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 8: Check if Fetch Needed │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ // Inside observer.onQueryUpdate() │
│ if (!query.state.data || query.isStale()) { │
│ query.fetch() │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 9: Fetch Starts │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ query.fetch() { │
│ // Update state │
│ this.setState({ │
│ fetchStatus: 'fetching' │
│ }) │
│ │
│ // Execute queryFn │
│ this.promise = fetchUsers() │
│ .then(data => { │
│ this.setState({ │
│ data, │
│ status: 'success', │
│ fetchStatus: 'idle', │
│ dataUpdatedAt: Date.now() │
│ }) │
│ }) │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 10: State Updates Trigger Notifications │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ query.setState(update) { │
│ // Merge state │
│ this.state = { ...this.state, ...update } │
│ │
│ // Notify all observers │
│ this.notify() │
│ } │
│ │
│ query.notify() { │
│ notifyManager.batch(() => { │
│ this.observers.forEach(observer => { │
│ observer.onQueryUpdate() │
│ }) │
│ }) │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 11: Observer Receives Update │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ observer.onQueryUpdate() { │
│ // Create new result │
│ const result = this.createResult() │
│ │
│ // Check if changed │
│ if (this.hasResultChanged(result)) { │
│ this.currentResult = result │
│ │
│ // Notify React via useSyncExternalStore │
│ this.listeners.forEach(listener => { │
│ listener() // Triggers React re-render │
│ }) │
│ } │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Step 12: React Re-renders Component │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ function UserList() { │
│ // useSyncExternalStore returns new snapshot │
│ const { data, isLoading } = useQuery(...) │
│ │
│ // Component re-renders with new data │
│ return <List data={data} /> │
│ } │
└─────────────────────────────────────────────────────────────┘












⚛️ React Integration {#react-integration}






Why useSyncExternalStore?



React 18 introduced useSyncExternalStore for exactly this use case:




Subscribing to external stores (non-React state) safely with concurrent rendering.







The Problem Without It:



Before React 18:




// Old approach (tearing risk)
function useExternalStore(store) {
const [state, setState] = useState(store.getState())

useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState())
})
return unsubscribe
}, [])

return state
}






Problem: Tearing in concurrent mode




  • Component might render with stale data

  • Inconsistent UI during suspense






The Solution:



useSyncExternalStore guarantees:




  • ✅ No tearing

  • ✅ Concurrent-safe

  • ✅ Consistent snapshots









How React Query Uses It:






function useQuery(options) {
const queryClient = useQueryClient()
const observer = useMemo(
() => new QueryObserver(queryClient, options),
[]
)

// Update options every render
observer.setOptions(options)

// Subscribe to observer
const result = useSyncExternalStore(
// Subscribe function
React.useCallback(
(onStoreChange) => observer.subscribe(onStoreChange),
[observer]
),

// Get current snapshot (client)
() => observer.getCurrentResult(),

// Get server snapshot (SSR)
() => observer.getCurrentResult()
)

return result
}









What This Does:





  1. Subscribe function:




    • Called once on mount

    • Returns unsubscribe function

    • React manages the subscription




  2. Get snapshot:




    • Called on every render

    • Returns current state

    • Must be pure and fast




  3. React handles:




    • Subscribing/unsubscribing

    • Detecting changes

    • Triggering re-renders

    • Avoiding tearing











👁️ QueryObserver Deep Dive {#queryobserver-deep-dive}






What is QueryObserver?



The bridge between React and Query.






Why Not Subscribe Directly?






// ❌ Why not this?
function useQuery(options) {
const query = getQuery(options.queryKey)
const [state, setState] = useState(query.state)

useEffect(() => {
return query.subscribe(() => setState(query.state))
}, [])

return state
}






Problems:




  • ❌ Re-renders on ANY state change (even unrelated)

  • ❌ No property tracking

  • ❌ No smart diffing

  • ❌ No batching






QueryObserver Solves This:






class QueryObserver {
// Track which properties component accessed
trackedProps = new Set()

createResult() {
const query = this.currentQuery
const state = query.state

// Create proxy to track access
return new Proxy({
data: state.data,
error: state.error,
isLoading: state.status === 'pending',
// ... more properties
}, {
get: (target, prop) => {
// Track that component accessed this prop
this.trackedProps.add(prop)
return target[prop]
}
})
}

hasResultChanged(result) {
// Only check tracked props
for (const key of this.trackedProps) {
if (this.currentResult[key] !== result[key]) {
return true // Re-render needed
}
}
return false // Skip re-render
}
}









Example:






function Component() {
// Only accesses 'data'
const { data } = useQuery(['users'], fetchUsers)
return <div>{data}</div>
}

// Tracked props: ['data']
// Won't re-render when isLoading, error, etc. change!












🔄 useSyncExternalStore Explained {#usesyncexternalstore-explained}






API:






useSyncExternalStore<Snapshot>(
subscribe: (onStoreChange: () => void) => () => void,
getSnapshot: () => Snapshot,
getServerSnapshot?: () => Snapshot
): Snapshot









Parameters:






1. subscribe



Called once on mount.




const subscribe = (onStoreChange) => {
// Add listener
const unsubscribe = observer.subscribe(onStoreChange)

// Return cleanup
return unsubscribe
}






When observer detects change:




// Inside QueryObserver
if (this.hasResultChanged(newResult)) {
// Notify React
this.listeners.forEach(listener => listener())
}









2. getSnapshot



Called on every render.




const getSnapshot = () => {
return observer.getCurrentResult()
}






Must be:




  • ✅ Pure (same input → same output)

  • ✅ Fast (called frequently)

  • ✅ Synchronous

  • ✅ Returns immutable snapshot



React compares snapshots:




const prev = getSnapshot()
// ... time passes ...
const next = getSnapshot()

if (prev !== next) {
// Re-render component
}









3. getServerSnapshot (optional)



For SSR/SSG.



Returns initial state during server rendering.









How React Uses It:






Component Render

Call getSnapshot() → Get current state

Compare with previous snapshot

Different? → Re-render
Same? → Skip render

Wait for notification from subscribe callback

On notification → Call getSnapshot() again












Why This Is Safe:



Concurrent rendering problem:




// Component might render multiple times before committing
Render 1: getSnapshot() { count: 0 }
Render 2: getSnapshot() { count: 1 } // Store updated
Render 3: getSnapshot() { count: 2 } // Store updated again
Commit: Use latest snapshot { count: 2 }






useSyncExternalStore ensures:




  • Always uses consistent snapshot

  • No partial/stale states

  • No tearing









🎨 Re-render Logic {#re-render-logic}






When Does React Query Re-render?



Decision tree:




Query state changes

Query.notify() called

All observers notified

QueryObserver.onQueryUpdate()

Create new result snapshot

Compare with previous result

├─ No tracked props changed
│ ↓
│ Skip notification
│ (No re-render)

└─ Tracked props changed

Notify React via listener

useSyncExternalStore detects change

Component re-renders












Example Scenarios:






Scenario 1: Only 'data' accessed






function Component() {
const { data } = useQuery(['users'], fetchUsers)
return <div>{data?.length} users</div>
}

// Tracked: ['data']
// Re-renders only when data changes






Timeline:




t0: { data: undefined, isLoading: true }
→ No re-render (data unchanged)

t1: { data: [...], isLoading: false }
→ RE-RENDER (data changed)

t2: { data: [...], isFetching: true }
→ No re-render (data unchanged)

t3: { data: [...], isFetching: false }
→ No re-render (data unchanged)












Scenario 2: Multiple properties accessed






function Component() {
const { data, isLoading } = useQuery(['users'], fetchUsers)

if (isLoading) return <Spinner />
return <div>{data?.length} users</div>
}

// Tracked: ['data', 'isLoading']
// Re-renders when either changes






Timeline:




t0: { data: undefined, isLoading: true }
→ No re-render (initial)

t1: { data: undefined, isLoading: true, isFetching: true }
→ No re-render (tracked props unchanged)

t2: { data: [...], isLoading: false }
→ RE-RENDER (both changed)












Scenario 3: Override tracking






const { data } = useQuery(['users'], fetchUsers, {
notifyOnChangeProps: ['data'] // Only notify for data
})

// Forces tracking of only 'data'
// Even if component accesses other props












Optimization Tips:



1. Only destructure what you need:




// ❌ Bad - tracks all props
const result = useQuery(['users'], fetchUsers)
return <div>{result.data}</div>

// ✅ Good - tracks only data
const { data } = useQuery(['users'], fetchUsers)
return <div>{data}</div>






2. Use notifyOnChangeProps for heavy components:




const { data } = useQuery(['users'], fetchUsers, {
notifyOnChangeProps: ['data']
})






3. Use select for derived state:




const { data: userCount } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select: (data) => data.length // Memoized!
})
// Only re-renders when length changes












💻 Real Code Walkthrough {#real-code-walkthrough}






Complete Example:






import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query'

// 1. Create QueryClient
const queryClient = new QueryClient()

// 2. Provide to app
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
)
}

// 3. Component using useQuery
function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('/api/users')
return res.json()
}
})

if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>

return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}









What Actually Happens:






App Render

QueryClientProvider creates context

UserList mounts

useQuery called

├─ useQueryClient() → gets QueryClient from context

├─ useMemo() → creates QueryObserver (once)
│ ↓
│ new QueryObserver(queryClient, options)

├─ observer.setOptions() → updates options

└─ useSyncExternalStore() → subscribes to observer

observer.subscribe(onStoreChange) called

QueryCache.build() → gets/creates Query

Query.subscribe(observer) → adds observer to set

Query checks if fetch needed

Yes → Query.fetch() starts

Query.setState({ fetchStatus: 'fetching' })

Query.notify() → notifies observers

QueryObserver.onQueryUpdate() → creates new result

Compares { data: undefined, isLoading: true }
with previous (undefined)

Changed! → calls listener()

useSyncExternalStore detects change

Component re-renders

Shows "Loading..."

... fetch completes ...

Query.setState({ data: [...], status: 'success' })

Query.notify()

Observer compares { data: [...], isLoading: false }

Changed! → notify React

Component re-renders

Shows user list












⚡ Performance Optimizations {#performance-optimizations}






1. Structural Sharing



Prevents unnecessary re-renders by reusing unchanged references.




// Old data
const oldData = { users: [{ id: 1, name: 'John' }] }

// New data (same content)
const newData = { users: [{ id: 1, name: 'John' }] }

// After structural sharing
oldData.users[0] === newData.users[0] // true!
// React component using users[0] won't re-render












2. Batched Notifications



Multiple state changes → single render.




// Inside NotifyManager
class NotifyManager {
batch(callback) {
// Collect all notifications
this.isBatching = true
callback()
this.isBatching = false

// Flush once
this.flush()
}

schedule(listener) {
if (this.isBatching) {
// Queue it
this.queue.add(listener)
} else {
// Execute immediately
listener()
}
}

flush() {
// Execute all queued notifications
this.queue.forEach(listener => listener())
this.queue.clear()
}
}






Result:




// Multiple updates
query.setState({ fetchStatus: 'fetching' })
query.setState({ data: [...] })
query.setState({ status: 'success' })

// Only ONE React render at the end!












3. Property Tracking



Only re-render when accessed properties change.




const { data } = useQuery(['users'], fetchUsers)

// Tracked: ['data']
// Ignores changes to: isLoading, error, isFetching, etc.












4. Fetch Deduplication



Same query requested multiple times → single network request.




// 10 components mount simultaneously
function Component() {
useQuery(['users'], fetchUsers)
}

// Only 1 network request!
// All components share the same Query.promise












🧠 Mental Models {#mental-models}






Model 1: Query as Database Row






Query = Row in database
QueryKey = Primary key
QueryCache = Database table
QueryObserver = Database subscription












Model 2: Pub/Sub Pattern






Query = Publisher
QueryObserver = Subscriber
Query.notify() = Publish event
Observer.onQueryUpdate() = Handle event












Model 3: React Integration






React Query = External store
useSyncExternalStore = React's bridge to external world
QueryObserver = Adapter that speaks React
Query = Pure async state machine












🎯 Key Takeaways






Architecture:





  1. Layered design - Clean separation of concerns


  2. Framework agnostic - Core logic independent of React


  3. Observer pattern - Fine-grained subscriptions


  4. useSyncExternalStore - Safe concurrent rendering






Flow:




  1. Component calls useQuery

  2. Creates QueryObserver (bridge)

  3. Subscribes via useSyncExternalStore

  4. Observer gets/creates Query from cache

  5. Query fetches if needed

  6. State changes notify observers

  7. Observers diff and notify React

  8. React re-renders component






Optimizations:





  1. Structural sharing - Reuse unchanged references


  2. Property tracking - Only re-render when needed


  3. Batched notifications - Multiple updates → single render


  4. Fetch deduplication - Share promises









📌 Final Thought



React Query's architecture is brilliant because:



Separation of concerns - Each layer has clear responsibility


Framework agnostic - Core can work with any framework


Performance first - Smart diffing and batching


Concurrent safe - Uses React 18's best practices


Developer friendly - Simple API, complex internals



Understanding this architecture helps you:




  • Debug effectively

  • Optimize performance

  • Build advanced patterns

  • Interview confidently

  • Appreciate the engineering









🚀 Next Steps




  1. Read useSyncExternalStore RFC

  2. Explore React Query source code

  3. Build custom QueryObserver

  4. Profile with React DevTools Profiler

  5. Contribute to React Query









💡 Pro Tips



Deepen your understanding:




  • Study the QueryObserver source

  • Build a mini React Query clone

  • Profile your app's re-renders

  • Use React DevTools Profiler









🤝 Let's Connect



Found this helpful?




  • ❤️ Save for future reference

  • 🔄 Share with your team

  • 💬 Ask questions in comments

  • 🐦 Follow for more deep dives



What part of the architecture surprised you most? Comment below! 👇






🔗 GitHub Repository:


https://github.com/TanStack/query






Happy architecting! 🎯



Tags: #react #tanstack #reactquery #javascript #webdev #architecture #usesyncexternalstore #performance

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten React Query Architecture — Complete Flow from Hook to Render

Thematisch verwandte Begriffe: React, Query, Architecture, Complete · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94040 | A flaw has been found in vas3k TaxHacker up to 0.8.5. Affected by this v…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick