query icon indicating copy to clipboard operation
query copied to clipboard

refactor(core): add generic utilities for resolving value-or-function patterns, replace specialized `resolveStaleTime` and `resolveEnabled`

Open braden-w opened this issue 8 months ago • 7 comments

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.

braden-w avatar May 29 '25 04:05 braden-w

🤖 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

nx-cloud[bot] avatar May 29 '25 04:05 nx-cloud[bot]

More templates

@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

pkg-pr-new[bot] avatar May 29 '25 16:05 pkg-pr-new[bot]

hm, the failing pipeline shows a lot of type-errors

TkDodo avatar Jun 02 '25 16:06 TkDodo

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.

TkDodo avatar Aug 19 '25 10:08 TkDodo

Thanks for the ping! Will update it and get back to you by EOD.

braden-w avatar Aug 19 '25 19:08 braden-w

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 logic
packages/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 logic
packages/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 flow
packages/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 handling
packages/query-core/src/retryer.ts
Replaced manual function/number handling for retryDelay with resolveOption(retryDelay, failureCount, error); imported resolveOption; core retry flow unchanged.
Utilities
packages/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 surface
packages/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 resolveOption and 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.

coderabbitai[bot] avatar Aug 19 '25 19:08 coderabbitai[bot]

⚠️ 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

changeset-bot[bot] avatar Oct 04 '25 18:10 changeset-bot[bot]