You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
hydrate() in @tanstack/query-core throws TypeError: Cannot read properties of undefined (reading 'mutations') when called with undefined. A defensive guard that made this a no-op was removed in @tanstack/query-core@5.102.1 and is still absent in the latest (5.102.5). This breaks TanStack Start SSR streaming: the client QueryClient never finishes hydrating and every mutation silently no-ops (no network request fired) while SSR markup and query polling keep working, so the failure is easy to miss.
The regression
hydrate() used to bail early on a non-object/null argument. That guard is gone as of 5.102.1.
Verified against the published packages (grep for dehydratedState !== "object" in build/modern/hydration.js): 5.102.0 → 1 match, 5.102.1 → 0, 5.102.5 → 0.
Who calls hydrate(client, undefined)
The TanStack Start SSR-query integration (@tanstack/router-ssr-query-core, currently frozen at 1.169.1, pulled in via @tanstack/react-router-ssr-query) installs a client-side router.options.hydrate that reads a stream of dehydrated query chunks and calls hydrate on every read — including the terminal { done: true, value: undefined } read — before it checks done:
// @tanstack/router-ssr-query-core@1.169.1/dist/esm/index.jsconstreader=dehydrated.queryStream.getReader();reader.read().then(asyncfunctionhandle({ done, value }){hydrate(queryClient,value,hydrateOptions);// value === undefined on the terminal readif(done)return;// done-check happens AFTER hydrate()returnhandle(awaitreader.read());}).catch((err)=>{console.error("Error reading query stream:",err);});
On the terminal read value is undefined. With the old guard, hydrate(client, undefined) was a safe no-op and the stream drained cleanly. Without it, hydrate dereferences undefined.mutations and throws. The throw is swallowed by the .catch (console.error("Error reading query stream:", …)), so hydration silently aborts partway: the client QueryClient is left un-hydrated and subsequent mutations never issue a network request — no error surfaced to the app, SSR HTML and polling queries still work, so it looks like the app is fine until you notice writes do nothing.
Symptom as seen in an app
Error reading query stream: TypeError: Cannot read properties of undefined (reading 'mutations')
at hydrate (query-core hydration.js)
at handle (@tanstack/react-router-ssr-query index.js)
(In a production/minified build this also shows up as React error #418 — hydration mismatch — because hydration bailed early.)
Reproduction
A TanStack Start app using setupRouterSsrQueryIntegration (i.e. @tanstack/react-router-ssr-query → @tanstack/router-ssr-query-core@1.169.1).
Any @tanstack/query-core >= 5.102.1 resolved for the client QueryClient.
SSR-render a route, let the dehydrated query stream drain on the client → the terminal hydrate(client, undefined) throws → mutations silently stop firing.
Pinning @tanstack/query-core (and @tanstack/react-query) back to 5.102.0 restores the guard and fixes it.
Restore the guard in query-core's hydrate() — bail when dehydratedState is not a non-null object. This is the historical, defensive behavior and matches the direction of fix(examples/vue/nuxt3): guard hydrate() against a null dehydrated state #11270 (guarding hydrate() against null/undefined dehydrated state in the Nuxt example). hydrate(client, undefined) being a no-op is a reasonable contract.
Have the SSR-query bridge check done before calling hydrate — on the terminal read value is undefined, so hydrate should not be called for it. (The bridge is currently frozen, so a fix here alone wouldn't help existing consumers on the pinned bridge version — hence option 1 is the more robust fix.)
Happy to open a PR restoring the guard if that's the preferred direction.
Summary
hydrate()in@tanstack/query-corethrowsTypeError: Cannot read properties of undefined (reading 'mutations')when called withundefined. A defensive guard that made this a no-op was removed in@tanstack/query-core@5.102.1and is still absent in the latest (5.102.5). This breaks TanStack Start SSR streaming: the clientQueryClientnever finishes hydrating and every mutation silently no-ops (no network request fired) while SSR markup and query polling keep working, so the failure is easy to miss.The regression
hydrate()used to bail early on a non-object/null argument. That guard is gone as of5.102.1.query-core@5.102.0—hydration.js(guard present):query-core@5.102.1..5.102.5— guard deleted:Verified against the published packages (grep for
dehydratedState !== "object"inbuild/modern/hydration.js):5.102.0→ 1 match,5.102.1→ 0,5.102.5→ 0.Who calls
hydrate(client, undefined)The TanStack Start SSR-query integration (
@tanstack/router-ssr-query-core, currently frozen at1.169.1, pulled in via@tanstack/react-router-ssr-query) installs a client-siderouter.options.hydratethat reads a stream of dehydrated query chunks and callshydrateon every read — including the terminal{ done: true, value: undefined }read — before it checksdone:On the terminal read
valueisundefined. With the old guard,hydrate(client, undefined)was a safe no-op and the stream drained cleanly. Without it,hydratedereferencesundefined.mutationsand throws. The throw is swallowed by the.catch(console.error("Error reading query stream:", …)), so hydration silently aborts partway: the clientQueryClientis left un-hydrated and subsequent mutations never issue a network request — no error surfaced to the app, SSR HTML and polling queries still work, so it looks like the app is fine until you notice writes do nothing.Symptom as seen in an app
(In a production/minified build this also shows up as React error #418 — hydration mismatch — because hydration bailed early.)
Reproduction
setupRouterSsrQueryIntegration(i.e.@tanstack/react-router-ssr-query→@tanstack/router-ssr-query-core@1.169.1).@tanstack/query-core >= 5.102.1resolved for the clientQueryClient.hydrate(client, undefined)throws → mutations silently stop firing.Pinning
@tanstack/query-core(and@tanstack/react-query) back to5.102.0restores the guard and fixes it.Environment
@tanstack/query-core: 5.102.1 – 5.102.5 (broken); 5.102.0 (works)@tanstack/react-query: 5.102.x@tanstack/react-router-ssr-query/@tanstack/router-ssr-query-core: 1.167.1 / 1.169.1@tanstack/react-start: 1.167.1,@tanstack/react-router: 1.168.1Suggested fixes (either resolves it)
query-core'shydrate()— bail whendehydratedStateis not a non-null object. This is the historical, defensive behavior and matches the direction of fix(examples/vue/nuxt3): guard hydrate() against a null dehydrated state #11270 (guardinghydrate()against null/undefined dehydrated state in the Nuxt example).hydrate(client, undefined)being a no-op is a reasonable contract.donebefore callinghydrate— on the terminal readvalueisundefined, sohydrateshould not be called for it. (The bridge is currently frozen, so a fix here alone wouldn't help existing consumers on the pinned bridge version — hence option 1 is the more robust fix.)Happy to open a PR restoring the guard if that's the preferred direction.