fix(query-core): update initialData when an observer mounts while a Query without data exists#9620
fix(query-core): update initialData when an observer mounts while a Query without data exists#9620
Conversation
…uery without data exists initialData gives the guarantee that data cannot be undefined, but this falls short when you create a Query via prefetching, and then mount the observer that has initialData set. That's because initialData is only doing something in the constructor of the Query, so if the Observer isn't the one who creates the Query, it does nothing This fix makes sure that when new options are applied on a Query (which e.g. happens when an observer mounts), initialData is taken into account when the Query doesn't have any data yet
WalkthroughIntroduces logic in Query.setOptions to seed initialData into state when no data exists, and adds a unit test verifying state transitions with initialData, prefetch, resolution, and reset behaviors. No public API signatures changed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant QC as QueryCache
participant Q as Query
participant O as QueryObserver
participant FN as queryFn
C->>QC: prefetchQuery(key, queryFn, { staleTime })
QC->>Q: ensureQuery(key)
Q->>FN: fetch (in-flight)
Note over Q: state: data=undefined<br/>status=pending, fetchStatus=fetching
C->>O: new QueryObserver({ initialData, initialDataUpdatedAt })
O->>Q: setOptions(...)
Q->>Q: getDefaultState(options)
alt no current data and defaultState has data
Q->>Q: setData(initialData, { manual:true, updatedAt: initialDataUpdatedAt })
end
O->>O: subscribe and receive state (success, fetching)
FN-->>Q: resolve('data')
Q-->>O: notify state (data='data', success, idle)
C->>QC: reset query(key)
QC->>Q: reset()
Q-->>O: notify state (initialData, idle)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
View your CI Pipeline Execution ↗ for commit a6269e9
☁️ Nx Cloud last updated this comment at |
|
Sizes for commit a6269e9:
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
packages/query-core/src/query.ts (1)
208-219: Minor: clarify guard and avoid unnecessary work
- Add an inline reason for the ESLint disable (state can be undefined during constructor’s initial setOptions).
- Micro-optimization: short-circuit when no initialData is provided to avoid computing defaultState.
Apply:
this.updateGcTime(this.options.gcTime) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.state && this.state.data === undefined) { + // During construction, `this.state` is undefined; this guard is required. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const hasInitial = + this.options.initialData !== undefined || + typeof this.options.initialData === 'function' + if (this.state && this.state.data === undefined && hasInitial) { const defaultState = getDefaultState(this.options) if (defaultState.data !== undefined) { this.setData(defaultState.data, { updatedAt: defaultState.dataUpdatedAt, manual: true, }) this.#initialState = defaultState } }packages/query-core/src/__tests__/query.test.tsx (2)
1196-1256: Use async timer advancement to avoid flakinessSwap to advanceTimersByTimeAsync for deterministic resolution of pending promises, and await both calls.
Apply:
- vi.advanceTimersByTime(50) + await vi.advanceTimersByTimeAsync(50) @@ - vi.advanceTimersByTime(50) + await vi.advanceTimersByTimeAsync(50)
1248-1248: Fix typo in test comment“ot” → “to”.
Apply:
- // resetting should get us back ot 'initialData' + // resetting should get us back to 'initialData'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/query-core/src/__tests__/query.test.tsx(1 hunks)packages/query-core/src/query.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/query-core/src/query.ts (1)
packages/query-core/src/mutation.ts (1)
getDefaultState(349-366)
packages/query-core/src/__tests__/query.test.tsx (2)
packages/query-core/src/utils.ts (1)
sleep(363-367)packages/query-core/src/query.ts (1)
promise(198-200)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Preview
- GitHub Check: Test
🔇 Additional comments (1)
packages/query-core/src/query.ts (1)
208-219: Initial data seeding on setOptions is correctThis achieves the intended behavior: seeds initialData only when no data exists, preserves fetchStatus via a manual success, and updates #initialState for reset. LGTM.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9620 +/- ##
===========================================
+ Coverage 45.47% 59.62% +14.15%
===========================================
Files 209 138 -71
Lines 8372 5615 -2757
Branches 1905 1511 -394
===========================================
- Hits 3807 3348 -459
+ Misses 4118 1963 -2155
+ Partials 447 304 -143 🚀 New features to boost your workflow:
|
initialData gives the guarantee that data cannot be undefined, but this falls short when you create a Query via prefetching, and then mount the observer that has initialData set.
That's because initialData is only doing something in the constructor of the Query, so if the Observer isn't the one who creates the Query, it does nothing
This fix makes sure that when new options are applied on a Query (which e.g. happens when an observer mounts), initialData is taken into account when the Query doesn't have any data yet
Summary by CodeRabbit
Bug Fixes
Tests