Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 41 additions & 51 deletions packages/react-query/src/HydrationBoundary.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,9 +29,6 @@ export const HydrationBoundary = ({
queryClient,
}: HydrationBoundaryProps) => {
const client = useQueryClient(queryClient)
const [hydrationQueue, setHydrationQueue] = React.useState<
DehydratedState['queries'] | undefined
>()

const optionsRef = React.useRef(options)
optionsRef.current = options
Expand All@@ -51,66 +48,59 @@ export const HydrationBoundary = ({
// If the transition is aborted, we will have hydrated any _new_ queries, but
// we throw away the fresh data for any existing ones to avoid unexpectedly
// updating the UI.
React.useMemo(() => {
if (state) {
if (typeof state !== 'object') {
return
}

const queryCache = client.getQueryCache()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const queries = (state as DehydratedState).queries || []

const newQueries: DehydratedState['queries'] = []
const existingQueries: DehydratedState['queries'] = []
for (const dehydratedQuery of queries) {
const existingQuery = queryCache.get(dehydratedQuery.queryHash)
const hydrationQueue: DehydratedState['queries'] | undefined =
React.useMemo(() => {
if (state) {
if (typeof state !== 'object') {
return
}

if (!existingQuery) {
newQueries.push(dehydratedQuery)
} else {
const hydrationIsNewer =
dehydratedQuery.state.dataUpdatedAt >
existingQuery.state.dataUpdatedAt ||
(dehydratedQuery.promise &&
existingQuery.state.status !== 'pending' &&
existingQuery.state.fetchStatus !== 'fetching' &&
dehydratedQuery.dehydratedAt !== undefined &&
dehydratedQuery.dehydratedAt > existingQuery.state.dataUpdatedAt)
const queryCache = client.getQueryCache()
// State is supplied from the outside and we might as well fail
// gracefully if it has the wrong shape, so while we type `queries`
// as required, we still provide a fallback.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const queries = (state as DehydratedState).queries || []

const queryAlreadyQueued = hydrationQueue?.find(
(query) => query.queryHash === dehydratedQuery.queryHash,
)
const newQueries: DehydratedState['queries'] = []
const existingQueries: DehydratedState['queries'] = []
for (const dehydratedQuery of queries) {
const existingQuery = queryCache.get(dehydratedQuery.queryHash)

if (
hydrationIsNewer &&
(!queryAlreadyQueued ||
if (!existingQuery) {
newQueries.push(dehydratedQuery)
} else {
const hydrationIsNewer =
dehydratedQuery.state.dataUpdatedAt >
queryAlreadyQueued.state.dataUpdatedAt)
) {
existingQueries.push(dehydratedQuery)
existingQuery.state.dataUpdatedAt ||
(dehydratedQuery.promise &&
existingQuery.state.status !== 'pending' &&
existingQuery.state.fetchStatus !== 'fetching' &&
dehydratedQuery.dehydratedAt !== undefined &&
dehydratedQuery.dehydratedAt >
existingQuery.state.dataUpdatedAt)

if (hydrationIsNewer) {
existingQueries.push(dehydratedQuery)
}
}
}
}

if (newQueries.length > 0) {
// It's actually fine to call this with queries/state that already exists
// in the cache, or is older. hydrate() is idempotent for queries.
hydrate(client, { queries: newQueries }, optionsRef.current)
}
if (existingQueries.length > 0) {
// eslint-disable-next-line react-hooks/react-compiler
setHydrationQueue((prev) =>
prev ? [...prev, ...existingQueries] : existingQueries,
)
if (newQueries.length > 0) {
// It's actually fine to call this with queries/state that already exists
// in the cache, or is older. hydrate() is idempotent for queries.
hydrate(client, { queries: newQueries }, optionsRef.current)
}
if (existingQueries.length > 0) {
return existingQueries
}
}
}
}, [client, hydrationQueue, state])
return undefined
}, [client, state])

React.useEffect(() => {
if (hydrationQueue) {
hydrate(client, { queries: hydrationQueue }, optionsRef.current)
setHydrationQueue(undefined)
}
}, [client, hydrationQueue])

Expand Down