Conversation
🦋 Changeset detectedLatest commit: 7c41b00 The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughDehydration now tags queries with Changes
Sequence Diagram(s)sequenceDiagram
participant Server as Server (prefetch)
participant Dehydrator as Dehydrator
participant Serialized as Dehydrated State
participant Client as Client (hydrate)
participant QueryClient as QueryClient
Server->>Dehydrator: prefetchInfiniteQuery (produce pages, pageParams)
Dehydrator->>Serialized: dehydrate → include query + queryType:'infiniteQuery'
Serialized-->>Client: transfer dehydrated state
Client->>QueryClient: hydrate(state)
QueryClient->>QueryClient: restore queries, read queryType
alt queryType == 'infiniteQuery'
QueryClient->>QueryClient: set behavior = infiniteQueryBehavior()
end
Client->>QueryClient: fetchInfiniteQuery() → uses restored pages & pageParams
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 4m 20s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 2s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-01-30 09:25:05 UTC
packages/query-core/src/hydration.ts
Outdated
| const queryOptions: any = { | ||
| ...client.getDefaultOptions().hydrate?.queries, | ||
| ...options?.defaultOptions?.queries, | ||
| queryKey, | ||
| queryHash, | ||
| meta, | ||
| } |
There was a problem hiding this comment.
can we keep this inlined please and just conditionally assign behavior:
behavior: queryType === 'infiniteQuery'
? (infiniteQueryBehavior() as QueryBehavior<unknown, unknown, unknown>)
: undefined,
There was a problem hiding this comment.
this is still not addressed
There was a problem hiding this comment.
Removed any from queryOptions and changed the cast to QueryBehavior<unknown, DefaultError, unknown>.
Is this the direction you had in mind?
packages/query-core/src/hydration.ts
Outdated
| const isRejectedThenable = | ||
| promise && | ||
| typeof promise === 'object' && | ||
| 'status' in promise && | ||
| (promise as any).status === 'rejected' | ||
|
|
||
| if (!isRejectedThenable) { | ||
| query | ||
| .fetch(undefined, { | ||
| // RSC transformed promises are not thenable | ||
| initialPromise: Promise.resolve(promise).then((resolvedData) => { | ||
| return deserializeData(resolvedData) | ||
| }), | ||
| }) | ||
| // Avoid unhandled promise rejections | ||
| .catch(noop) |
There was a problem hiding this comment.
I don’t understand what this change has to do with infinite queries ?
161c143 to
7c41b00
Compare
|
Hi @TkDodo, gentle ping! Let me know if there's anything else to address. |
packages/query-core/src/hydration.ts
Outdated
| } | ||
|
|
||
| function isInfiniteQuery(query: Query): boolean { | ||
| const options = query.options as any |
There was a problem hiding this comment.
the as any seems super unnecessary
| const options = query.options as any | |
| const options = query.options |
There was a problem hiding this comment.
Removed as any from query.options.
packages/query-core/src/hydration.ts
Outdated
| const queryOptions: any = { | ||
| ...client.getDefaultOptions().hydrate?.queries, | ||
| ...options?.defaultOptions?.queries, | ||
| queryKey, | ||
| queryHash, | ||
| meta, | ||
| } |
There was a problem hiding this comment.
this is still not addressed
- Remove isRejectedThenable check - Inline behavior assignment
7c41b00 to
9e1ee4d
Compare
9e1ee4d to
47b6409
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/query-core/src/hydration.ts`:
- Around line 75-78: The isInfiniteQuery check loses the infinite-query marker
because rebuilt queries (around the rebuild logic at lines 258-267) only copy
behavior and drop options like initialPageParam; update the rebuild logic so
when reconstructing a Query you preserve options.initialPageParam (or any
presence of that key) from the original query.options into the new options
object so isInfiniteQuery(query) still returns true; specifically, in the code
that recreates the query instance, copy over initialPageParam if it exists (or
spread the original options) so the isInfiniteQuery function and subsequent
dehydrate() keep the entry as an infinite query.
- Around line 14-16: The import ordering currently violates the lint rule
because the value import infiniteQueryBehavior is placed after type-only
imports; move the value import "infiniteQueryBehavior" so it appears before the
type-only imports "Query, QueryBehavior, QueryState" and "Mutation,
MutationState" (i.e., import infiniteQueryBehavior first, then the type-only
imports) to satisfy the import/order rule while keeping the same symbols and
module names.
- Around line 258-267: The merge for queryOptions is overwriting any
caller-provided hydrate.behavior with explicit undefined because behavior is
always assigned; change it to only set behavior when queryType ===
'infiniteQuery' so existing behavior from
client.getDefaultOptions().hydrate?.queries or options?.defaultOptions?.queries
is preserved. Concretely, build the base merge of
client.getDefaultOptions().hydrate?.queries and options?.defaultOptions?.queries
into queryOptions (including queryKey/queryHash/meta), then conditionally add
behavior using infiniteQueryBehavior() only when queryType === 'infiniteQuery'
(or spread { behavior: ... } in that branch) instead of assigning behavior:
undefined in the default path; update the code around the queryOptions object
(symbols: queryOptions, client.getDefaultOptions().hydrate?.queries,
options?.defaultOptions?.queries, behavior, infiniteQueryBehavior, queryType)
accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8dab147c-bba2-400f-8e55-09237251b4e8
📒 Files selected for processing (1)
packages/query-core/src/hydration.ts
| import type { Query, QueryBehavior, QueryState } from './query' | ||
| import type { Mutation, MutationState } from './mutation' | ||
| import { infiniteQueryBehavior } from './infiniteQueryBehavior' |
There was a problem hiding this comment.
Fix the import/order failure.
Move import { infiniteQueryBehavior } from './infiniteQueryBehavior' above the type-only imports. Line 16 currently trips the ESLint rule.
🧰 Tools
🪛 ESLint
[error] 16-16: ./infiniteQueryBehavior import should occur before type import of ./types
(import/order)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/query-core/src/hydration.ts` around lines 14 - 16, The import
ordering currently violates the lint rule because the value import
infiniteQueryBehavior is placed after type-only imports; move the value import
"infiniteQueryBehavior" so it appears before the type-only imports "Query,
QueryBehavior, QueryState" and "Mutation, MutationState" (i.e., import
infiniteQueryBehavior first, then the type-only imports) to satisfy the
import/order rule while keeping the same symbols and module names.
| function isInfiniteQuery(query: Query): boolean { | ||
| const options = query.options | ||
| return 'initialPageParam' in options | ||
| } |
There was a problem hiding this comment.
isInfiniteQuery becomes lossy after this hydration path.
Lines 258-267 rebuild infinite queries with behavior only, so the original initialPageParam marker is lost here. Unless hydrate defaults happen to re-add it, Line 77 flips back to false on the rebuilt query, and a later dehydrate() serializes the same entry as a regular query again. That reopens a hydrate→dehydrate round-trip gap for the pending infinite-query path this change is fixing.
Also applies to: 258-267
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/query-core/src/hydration.ts` around lines 75 - 78, The
isInfiniteQuery check loses the infinite-query marker because rebuilt queries
(around the rebuild logic at lines 258-267) only copy behavior and drop options
like initialPageParam; update the rebuild logic so when reconstructing a Query
you preserve options.initialPageParam (or any presence of that key) from the
original query.options into the new options object so isInfiniteQuery(query)
still returns true; specifically, in the code that recreates the query instance,
copy over initialPageParam if it exists (or spread the original options) so the
isInfiniteQuery function and subsequent dehydrate() keep the entry as an
infinite query.
| const queryOptions = { | ||
| ...client.getDefaultOptions().hydrate?.queries, | ||
| ...options?.defaultOptions?.queries, | ||
| queryKey, | ||
| queryHash, | ||
| meta, | ||
| behavior: queryType === 'infiniteQuery' | ||
| ? (infiniteQueryBehavior() as QueryBehavior<unknown, DefaultError, unknown>) | ||
| : undefined, | ||
| } |
There was a problem hiding this comment.
Don't overwrite caller-provided hydrate behavior with undefined.
Because Lines 264-266 are assigned last, non-infinite or legacy payloads now overwrite any hydrate.defaultOptions.queries.behavior with undefined. The previous merge preserved that option, so this is a behavior regression for custom hydration setups.
Suggested fix
const queryOptions = {
...client.getDefaultOptions().hydrate?.queries,
...options?.defaultOptions?.queries,
queryKey,
queryHash,
meta,
- behavior: queryType === 'infiniteQuery'
- ? (infiniteQueryBehavior() as QueryBehavior<unknown, DefaultError, unknown>)
- : undefined,
+ ...(queryType === 'infiniteQuery'
+ ? {
+ behavior: infiniteQueryBehavior() as QueryBehavior<
+ unknown,
+ DefaultError,
+ unknown
+ >,
+ }
+ : {}),
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const queryOptions = { | |
| ...client.getDefaultOptions().hydrate?.queries, | |
| ...options?.defaultOptions?.queries, | |
| queryKey, | |
| queryHash, | |
| meta, | |
| behavior: queryType === 'infiniteQuery' | |
| ? (infiniteQueryBehavior() as QueryBehavior<unknown, DefaultError, unknown>) | |
| : undefined, | |
| } | |
| const queryOptions = { | |
| ...client.getDefaultOptions().hydrate?.queries, | |
| ...options?.defaultOptions?.queries, | |
| queryKey, | |
| queryHash, | |
| meta, | |
| ...(queryType === 'infiniteQuery' | |
| ? { | |
| behavior: infiniteQueryBehavior() as QueryBehavior< | |
| unknown, | |
| DefaultError, | |
| unknown | |
| >, | |
| } | |
| : {}), | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/query-core/src/hydration.ts` around lines 258 - 267, The merge for
queryOptions is overwriting any caller-provided hydrate.behavior with explicit
undefined because behavior is always assigned; change it to only set behavior
when queryType === 'infiniteQuery' so existing behavior from
client.getDefaultOptions().hydrate?.queries or options?.defaultOptions?.queries
is preserved. Concretely, build the base merge of
client.getDefaultOptions().hydrate?.queries and options?.defaultOptions?.queries
into queryOptions (including queryKey/queryHash/meta), then conditionally add
behavior using infiniteQueryBehavior() only when queryType === 'infiniteQuery'
(or spread { behavior: ... } in that branch) instead of assigning behavior:
undefined in the default path; update the code around the queryOptions object
(symbols: queryOptions, client.getDefaultOptions().hydrate?.queries,
options?.defaultOptions?.queries, behavior, infiniteQueryBehavior, queryType)
accordingly.

Fixes #8825
Problem
When
prefetchInfiniteQueryfails on the server,useSuspenseInfiniteQueryon the client doesn't return the correct infinite query structure ({ pages: [], pageParams: [] }), causing a runtime error when accessingdata.pages:Root Cause
Infinite query behavior information gets lost during hydration:
infiniteQueryBehavioris attached whenprefetchInfiniteQueryruns on the serverInfiniteQueryObserverattaches the behaviorSolution
Set up infinite query behavior at hydration time to guarantee correct data structure for both success and failure cases.
Changes
1. Added
queryTypefield toDehydratedQuery'query' | 'infiniteQuery'value2. Auto-detect query type during dehydration
initialPageParampresence to identify infinite queriesqueryTypefield3. Auto-configure behavior during hydration
infiniteQueryBehaviorwhenqueryTypeis'infiniteQuery'4. Handle failed promises
initialPromiseModified Files
packages/query-core/src/hydration.tspackages/query-core/src/__tests__/hydration.test.tsxTests
All 35 tests passing (including 2 new tests)
New tests added:
should preserve queryType for infinite queries during hydrationshould attach infiniteQueryBehavior during hydrationChecklist
queryType: 'query' | 'infiniteQuery'field toDehydratedQueryinitialPageParaminfiniteQueryBehaviorfor infinite queries during hydrationSummary by CodeRabbit
Bug Fixes
Tests
Chores