Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

braden-w
Copy link
Contributor

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.

@braden-w braden-w changed the title refactor(core): add generic utilities for resolving value-or-function patterns refactor(core): add generic utilities for resolving value-or-function patterns, replace specialized resolveStaleTime and resolveEnabled May 29, 2025
Copy link

nx-cloud bot commented May 29, 2025

View your CI Pipeline Execution ↗ for commit af723ec.

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ❌ Failed 2m 52s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 43s View ↗

☁️ Nx Cloud last updated this comment at 2025-06-02 16:31:27 UTC

@braden-w braden-w force-pushed the refactor/resolveValueOrFunction branch from df0249f to 2421a38 Compare May 29, 2025 04:59
… patterns, replace specialized `resolveStaleTime` and `resolveEnabled`

This commit introduces `isFunctionVariant()` and `resolveValueOrFunction()` utilities to replace repetitive `typeof === 'function'` checks throughout the codebase, consolidating the common "value or function that computes value" pattern.
@braden-w braden-w force-pushed the refactor/resolveValueOrFunction branch from 409a222 to b28e950 Compare May 29, 2025 05:05
… NonFunctionGuard

This simplification is possible due to the introduction of generic runtime
utilities that handle value-or-function resolution. The new `resolveValueOrFunction()` utility handles the distinction between direct values and functions at runtime with proper type safety, eliminating the  need for complex type-level guards.
@braden-w braden-w force-pushed the refactor/resolveValueOrFunction branch from b28e950 to cd59cc4 Compare May 29, 2025 16:31
Copy link

pkg-pr-new bot commented May 29, 2025

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: af723ec

@braden-w braden-w force-pushed the refactor/resolveValueOrFunction branch from 9d15984 to 427c977 Compare May 29, 2025 16:48
Copy link
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree with the direction, please have a look at the comments though

@@ -58,7 +58,8 @@
"@angular/platform-browser-dynamic": "^20.0.0",
"@tanstack/angular-query-experimental": "workspace:*",
"eslint-plugin-jsdoc": "^50.5.0",
"npm-run-all2": "^5.0.0"
"npm-run-all2": "^5.0.0",
"tsup": "^8.4.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was adding tsup necessary here? I don’t think angular uses tsup for bundling 🤔

): T {
return isFunctionVariant(value) ? value(...args) : value
}

export function functionalUpdate<TInput, TOutput>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use resolveValueOrFunction here as well, as this also uses a typeof updater === 'function' check ?

* const delay = resolveValueOrFunction(retryDelay, failureCount, error)
* ```
*/
export function resolveValueOrFunction<T, TArgs extends Array<any>>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a name like resolveOption would be descriptive enough

* ```
*/
export function resolveValueOrFunction<T, TArgs extends Array<any>>(
value: T | ((...args: TArgs) => T),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we should limit T with NonFunctionGuard like we do here:

placeholderData?:
| NonFunctionGuard<TQueryData>
| PlaceholderDataFunction<
NonFunctionGuard<TQueryData>,
TError,
NonFunctionGuard<TQueryData>,
TQueryKey
>

otherwise, T could be a function itself and then everything becomes ambiguous

@TkDodo
Copy link
Collaborator

TkDodo commented Jun 2, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants