Open
Description
Describe the bug
Setting query options like gcTime
or staleTime
seem to get lost on the client and replaced with default values after hydration.
Your minimal, reproducible example
https://codesandbox.io/p/github/andyabih/tmp-start-query-options/main
Steps to reproduce
- Click on "Click here to test Test Query"
- Refresh and wait for full page load
- Click on "Increment" a few times, the options should reset to defaults.
Expected behavior
Options to persist through the hydration process.
How often does this bug happen?
Often
Screenshots or Videos
TSIssue.mov
Platform
macOS - Arc
Tanstack Query adapter
react-query
TanStack Query version
5.66.0
TypeScript version
5.7.2
Additional context
Not sure if related, but I did notice that in the hydrate
and dehydrate
methods, only queryKey
, queryHash
, and meta
are being used in the objects.
function hydrate(client, dehydratedState, options) {
if (typeof dehydratedState !== "object" || dehydratedState === null) {
return;
}
const mutationCache = client.getMutationCache();
const queryCache = client.getQueryCache();
const deserializeData = options?.defaultOptions?.deserializeData ?? client.getDefaultOptions().hydrate?.deserializeData ?? defaultTransformerFn;
const mutations = dehydratedState.mutations || [];
const queries = dehydratedState.queries || [];
mutations.forEach(({ state, ...mutationOptions }) => {
mutationCache.build(
client,
{
...client.getDefaultOptions().hydrate?.mutations,
...options?.defaultOptions?.mutations,
...mutationOptions
},
state
);
});
queries.forEach(
({ queryKey, state, queryHash, meta, promise, dehydratedAt }) => {
const syncData = promise ? tryResolveSync(promise) : void 0;
const rawData = state.data === void 0 ? syncData?.data : state.data;
const data = rawData === void 0 ? rawData : deserializeData(rawData);
let query = queryCache.get(queryHash);
const existingQueryIsPending = query?.state.status === "pending";
const existingQueryIsFetching = query?.state.fetchStatus === "fetching";
if (query) {
const hasNewerSyncData = syncData && // We only need this undefined check to handle older dehydration
// payloads that might not have dehydratedAt
dehydratedAt !== void 0 && dehydratedAt > query.state.dataUpdatedAt;
if (state.dataUpdatedAt > query.state.dataUpdatedAt || hasNewerSyncData) {
const { fetchStatus: _ignored, ...serializedState } = state;
query.setState({
...serializedState,
data
});
}
} else {
query = queryCache.build(
client,
{
...client.getDefaultOptions().hydrate?.queries,
...options?.defaultOptions?.queries,
queryKey,
queryHash,
meta
},
// Reset fetch status to idle to avoid
// query being stuck in fetching state upon hydration
{
...state,
data,
fetchStatus: "idle",
status: data !== void 0 ? "success" : state.status
}
);
}
if (promise && !existingQueryIsPending && !existingQueryIsFetching && // Only hydrate if dehydration is newer than any existing data,
// this is always true for new queries
(dehydratedAt === void 0 || dehydratedAt > query.state.dataUpdatedAt)) {
void query.fetch(void 0, {
// RSC transformed promises are not thenable
initialPromise: Promise.resolve(promise).then(deserializeData)
});
}
}
);
}
Metadata
Metadata
Assignees
Labels
No labels