-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
fix(react-query): resolve hydration mismatch in SSR with prefetched queries #9572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
29eb336
ab2ddad
907def0
184be02
5f14a66
cb50d25
25fc95e
3429d75
c65c2f9
e69a205
7996780
af8b955
1bf795b
d2feecb
261326b
a0859fa
58065ac
d43b020
a8feb9e
e19ca01
253cb40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -264,6 +264,34 @@ export class QueryObserver< | |
return this.#currentResult | ||
} | ||
|
||
getServerResult(): QueryObserverResult<TData, TError> { | ||
const currentResult = this.#currentResult | ||
|
||
if ( | ||
currentResult.status === 'success' && | ||
this.#currentQuery.state.dataUpdatedAt === 0 | ||
) { | ||
const pendingResult: QueryObserverResult<TData, TError> = { | ||
|
||
...currentResult, | ||
status: 'pending', | ||
isPending: true, | ||
isSuccess: false, | ||
isError: false, | ||
isLoading: currentResult.fetchStatus === 'fetching', | ||
isInitialLoading: currentResult.fetchStatus === 'fetching', | ||
data: undefined, | ||
error: null, | ||
isLoadingError: false, | ||
isRefetchError: false, | ||
isPlaceholderData: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how did you come up with this list of fields to change? For example, why aren’t fields like I’m wondering how we can make sure to keep this list in-sync if we add new properties to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for your feedback! In conclusion, the criteria for selecting the specified fields was “only change fields that directly affect SSR hydration inconsistencies.” I believe that intentionally maintaining the currentResult value for the remaining fields will be beneficial during subsequent maintenance processes. However, based on coderabbitai's feedback, I realized that I had overlooked |
||
} as QueryObserverResult<TData, TError> | ||
|
||
return pendingResult | ||
} | ||
|
||
return currentResult | ||
} | ||
|
||
trackResult( | ||
result: QueryObserverResult<TData, TError>, | ||
onPropTracked?: (key: keyof QueryObserverResult) => void, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Use a non-zero constant (or set system time) to avoid flakiness with fake timers
Under fake timers, Date.now() may be 0 unless you set the system time. That would unintentionally mark this as “hydrated” (dataUpdatedAt === 0), causing
getServerResult
to mask the result and fail the “non-hydrated” expectations.Minimal, deterministic fix:
Alternatively, set the mocked clock first:
🏁 Script executed:
Length of output: 3741
Use a non-zero constant for
dataUpdatedAt
to avoid flakiness under fake timersUnder
vi.useFakeTimers()
,Date.now()
returns0
unless you explicitly advance the clock—making your “fresh” timestamp indistinguishable from the unhydrated state. Update the assignment inpackages/query-core/src/__tests__/queryObserver.test.tsx
to use a non-zero literal (or advance the fake clock first):• Minimal, consistent with other direct state mutations:
• Alternative (advance the fake clock, then read):
🤖 Prompt for AI Agents