refactor(core): add generic utilities for resolving value-or-function patterns, replace specialized `resolveStaleTime` and `resolveEnabled`
This PR introduces isFunctionVariant() and resolveValueOrFunction() utilities to replace repetitive typeof === 'function' checks throughout the codebase, consolidating the common "value or function that computes value" pattern.
Problem and Solution
The codebase had scattered implementations of the same pattern across multiple files:
// Repetitive pattern everywhere
const data = typeof options.initialData === 'function'
? options.initialData()
: options.initialData
const delay = typeof retryDelay === 'function'
? retryDelay(failureCount, error)
: retryDelay
This led to code duplication, inconsistency, and maintenance overhead. We also had specialized versions like resolveStaleTime() and resolveEnabled() that could be generalized.
The new utilities provide a clean, generic solution:
// Clean and intention-revealing
const data = resolveValueOrFunction(options.initialData)
const delay = resolveValueOrFunction(retryDelay, failureCount, error)
While we could inline typeof value === 'function' checks, TypeScript's type narrowing doesn't work properly with generic types in complex expressions. The isFunctionVariant() type guard provides proper type narrowing that allows resolveValueOrFunction() to safely call the function variant. Without it, TypeScript throws errors because it can't guarantee the type safety across the generic boundary.
Both utilities support zero-argument functions (() => T) and functions with parameters ((...args) => T), making them applicable to all value-or-function patterns in the codebase.
Updated implementations in query.ts, queryObserver.ts, and retryer.ts for handling initialData, retryDelay, refetchInterval, and notifyOnChangeProps. These utilities can replace existing resolveStaleTime() and resolveEnabled() functions in future iterations.
Summary by CodeRabbit
-
New Features
- Placeholder data can now be provided as either a direct value or a function for more flexible initial UI states.
-
Refactor
- Consolidated option handling into a single, consistent resolution approach used for enabled checks, stale timing, initial/placeholder data, retry delays, and observer notifications.
-
Chores
- Removed legacy per-option helpers and some exported helper types; introduced a unified option-resolver utility and consolidated related logic.
🤖 Nx Cloud AI Fix Eligible
An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.
To disable these notifications, a workspace admin can disable them in workspace settings.
View your CI Pipeline Execution ↗ for commit 85954600e8f95df28447c4b70978a29f160ba19c
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 2m 42s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
❌ Failed | 5s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-10-05 00:31:46 UTC
More templates
- @tanstack/query-example-angular-auto-refetching
- @tanstack/query-example-angular-basic
- @tanstack/query-example-angular-basic-persister
- @tanstack/query-example-angular-devtools-panel
- @tanstack/query-example-angular-infinite-query-with-max-pages
- @tanstack/query-example-angular-optimistic-updates
- @tanstack/query-example-angular-pagination
- @tanstack/query-example-angular-query-options-from-a-service
- @tanstack/query-example-angular-router
- @tanstack/query-example-angular-rxjs
- @tanstack/query-example-angular-simple
- @tanstack/query-example-react-algolia
- @tanstack/query-example-react-auto-refetching
- @tanstack/query-example-react-basic
- @tanstack/query-example-react-basic-graphql-request
- @tanstack/query-example-chat
- @tanstack/query-example-react-default-query-function
- @tanstack/query-example-react-devtools-panel
- @tanstack/query-example-eslint-legacy
- @tanstack/query-example-react-infinite-query-with-max-pages
- @tanstack/query-example-react-load-more-infinite-scroll
- @tanstack/query-example-react-nextjs
- @tanstack/query-example-react-nextjs-app-prefetching
- @tanstack/query-example-nextjs-suspense-streaming
- @tanstack/query-example-react-offline
- @tanstack/query-example-react-optimistic-updates-cache
- @tanstack/query-example-react-optimistic-updates-ui
- @tanstack/query-example-react-pagination
- @tanstack/query-example-react-playground
- @tanstack/query-example-react-prefetching
- @tanstack/query-example-react-react-native
- @tanstack/query-example-react-router
- @tanstack/query-example-react-rick-morty
- @tanstack/query-example-react-shadow-dom
- @tanstack/query-example-react-simple
- @tanstack/query-example-react-star-wars
- @tanstack/query-example-react-suspense
- @tanstack/query-example-solid-astro
- @tanstack/query-example-solid-basic
- @tanstack/query-example-solid-basic-graphql-request
- @tanstack/query-example-solid-default-query-function
- @tanstack/query-example-solid-simple
- @tanstack/query-example-solid-start-streaming
- @tanstack/query-example-svelte-auto-refetching
- @tanstack/query-example-svelte-basic
- @tanstack/query-example-svelte-load-more-infinite-scroll
- @tanstack/query-example-svelte-optimistic-updates
- @tanstack/query-example-svelte-playground
- @tanstack/query-example-svelte-simple
- @tanstack/query-example-svelte-ssr
- @tanstack/query-example-svelte-star-wars
- @tanstack/query-example-vue-2.6-basic
- @tanstack/query-example-vue-2.7-basic
- @tanstack/query-example-vue-basic
- @tanstack/query-example-vue-dependent-queries
- @tanstack/query-example-vue-nuxt3
- @tanstack/query-example-vue-persister
- @tanstack/query-example-vue-simple
@tanstack/angular-query-devtools-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-devtools-experimental@9212
@tanstack/angular-query-experimental
npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9212
@tanstack/eslint-plugin-query
npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9212
@tanstack/query-async-storage-persister
npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9212
@tanstack/query-broadcast-client-experimental
npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9212
@tanstack/query-core
npm i https://pkg.pr.new/@tanstack/query-core@9212
@tanstack/query-devtools
npm i https://pkg.pr.new/@tanstack/query-devtools@9212
@tanstack/query-persist-client-core
npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9212
@tanstack/query-sync-storage-persister
npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9212
@tanstack/react-query
npm i https://pkg.pr.new/@tanstack/react-query@9212
@tanstack/react-query-devtools
npm i https://pkg.pr.new/@tanstack/react-query-devtools@9212
@tanstack/react-query-next-experimental
npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9212
@tanstack/react-query-persist-client
npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9212
@tanstack/solid-query
npm i https://pkg.pr.new/@tanstack/solid-query@9212
@tanstack/solid-query-devtools
npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9212
@tanstack/solid-query-persist-client
npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9212
@tanstack/svelte-query
npm i https://pkg.pr.new/@tanstack/svelte-query@9212
@tanstack/svelte-query-devtools
npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9212
@tanstack/svelte-query-persist-client
npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9212
@tanstack/vue-query
npm i https://pkg.pr.new/@tanstack/vue-query@9212
@tanstack/vue-query-devtools
npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9212
commit: ae7a70b
hm, the failing pipeline shows a lot of type-errors
it’s been two months, there’s still a failing pipeline and conflicts. please let me know if you intend to finish this PR, because it doesn’t look like it’s close to working. I’ll close it otherwise.
Thanks for the ping! Will update it and get back to you by EOD.
Walkthrough
Replaced multiple per-option resolvers with a single exported resolveOption across query-core; updated query, observer, client, retryer, utils, and types to use it. Removed the exported InitialDataFunction and PlaceholderDataFunction exports; adjusted related types, imports, and a minor comment typo. (39 words)
Changes
| Cohort / File(s) | Summary of changes |
|---|---|
Core query logicpackages/query-core/src/query.ts |
Replaced resolveEnabled/resolveStaleTime usages with resolveOption; isActive/isStatic, initialData, and initialDataUpdatedAt now use resolveOption; removed InitialDataFunction import/usage; small comment typo fix. |
Observer logicpackages/query-core/src/queryObserver.ts |
Consolidated resolution of enabled, staleTime, refetchInterval, placeholderData, and notifyOnChangeProps to resolveOption; updated setOptions, update flow, optimistic/result construction; removed imports/exports of old per-option resolvers and PlaceholderDataFunction. |
Client fetch flowpackages/query-core/src/queryClient.ts |
Switched stale-time resolution to resolveOption in ensureQueryData and fetchQuery; replaced functionalUpdate(updater, prevData) with resolveOption(updater, prevData) in setQueryData; adjusted imports. |
Retry handlingpackages/query-core/src/retryer.ts |
Replaced manual function/number handling for retryDelay with resolveOption(retryDelay, failureCount, error); imported resolveOption; core retry flow unchanged. |
Utilitiespackages/query-core/src/utils.ts |
Added exported resolveOption and NonFunction/NonFunctionGuard types; updated Updater to use non-function guard; removed exported resolveStaleTime and resolveEnabled; replaced value-or-function branches with resolveOption; added JSDoc and examples. |
Types and public API surfacepackages/query-core/src/types.ts |
Added JSDoc for NonFunctionGuard<T>; removed exported InitialDataFunction and PlaceholderDataFunction (made non-exported/removed); adjusted related imports and exported types accordingly. |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant C as Consumer
participant QO as QueryObserver
participant Q as Query
participant QC as QueryClient
participant U as utils.resolveOption
participant R as Retryer
C->>QO: setOptions(options)
QO->>U: resolveOption(options.enabled, Q)
U-->>QO: isEnabled
QO->>U: resolveOption(options.staleTime, Q)
U-->>QO: staleTime
QO->>Q: request initial/placeholder data
Q->>U: resolveOption(options.initialData, Q)
U-->>Q: initialData
alt Needs fetch
QO->>QC: fetchQuery(key, options)
QC->>U: resolveOption(options.staleTime, Q)
U-->>QC: staleTime
QC->>R: start retryer
R->>U: resolveOption(options.retryDelay, failureCount, error)
U-->>R: delay
R-->>QC: result/error
QC-->>QO: result
QO-->>C: notify result
else No fetch
QO-->>C: notify cached result
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
- TanStack/query#9596 — updates docs and usages related to option values being functions (matches introduction of
resolveOptionand function-form option handling).
Suggested reviewers
- TkDodo
- arnoud-dv
- manudeli
Poem
I hop through fields of code so bright,
One resolver now to set things right.
Stale times, placeholders, retries too,
All resolved with a single view.
Happy hops — tidy changes, woohoo! 🐇
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Description Check | ⚠️ Warning | The pull request description does not follow the repository’s required template and is missing the “## 🎯 Changes” section as well as the checklist and release impact sections. | Please update the description to include the “## 🎯 Changes” section detailing the modifications, complete the “✅ Checklist” items, and add a “## 🚀 Release Impact” section per the repository template. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title Check | ✅ Passed | The title accurately summarizes the primary change by highlighting the introduction of generic utilities for resolving value-or-function patterns and the replacement of the specialized helpers resolveStaleTime and resolveEnabled, making it clear and specific without irrelevant details. |
| Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
📜 Recent review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between a8c2ddcae1f6861937c20fb5a6596f1a2bf75943 and 85954600e8f95df28447c4b70978a29f160ba19c.
📒 Files selected for processing (1)
-
packages/query-core/src/types.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/query-core/src/types.ts
Comment @coderabbitai help to get the list of available commands and usage tips.
⚠️ No Changeset found
Latest commit: 85954600e8f95df28447c4b70978a29f160ba19c
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR