diff --git a/.changeset/fix-mutation-state-generics.md b/.changeset/fix-mutation-state-generics.md new file mode 100644 index 00000000000..30ad7552586 --- /dev/null +++ b/.changeset/fix-mutation-state-generics.md @@ -0,0 +1,9 @@ +--- +'@tanstack/react-query': patch +'@tanstack/preact-query': patch +'@tanstack/solid-query': patch +'@tanstack/vue-query': patch +'@tanstack/svelte-query': patch +--- + +fix(types): propagate generic type parameters to `useMutationState` select callback diff --git a/packages/preact-query/src/__tests__/useMutationState.test-d.tsx b/packages/preact-query/src/__tests__/useMutationState.test-d.tsx index 6c8a544ebdb..679444258fe 100644 --- a/packages/preact-query/src/__tests__/useMutationState.test-d.tsx +++ b/packages/preact-query/src/__tests__/useMutationState.test-d.tsx @@ -1,4 +1,8 @@ -import type { MutationState, MutationStatus } from '@tanstack/query-core' +import type { + Mutation, + MutationState, + MutationStatus, +} from '@tanstack/query-core' import { describe, expectTypeOf, it } from 'vitest' import { useMutationState } from '../useMutationState' @@ -21,4 +25,19 @@ describe('useMutationState', () => { expectTypeOf(result).toEqualTypeOf>() }) + it('should propagate generics to select callback when TResult is typed MutationState', () => { + type MyData = { data: Array } + type MyError = { code: number; message: string } + type MyVars = { id: number } + + useMutationState>({ + filters: { mutationKey: ['key'] }, + select: (mutation) => { + expectTypeOf(mutation).toEqualTypeOf< + Mutation + >() + return mutation.state + }, + }) + }) }) diff --git a/packages/preact-query/src/useMutationState.ts b/packages/preact-query/src/useMutationState.ts index 61fcd1b1c9a..b998467925f 100644 --- a/packages/preact-query/src/useMutationState.ts +++ b/packages/preact-query/src/useMutationState.ts @@ -22,25 +22,50 @@ export function useIsMutating( ).length } -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/react-query/src/__tests__/useMutationState.test-d.tsx b/packages/react-query/src/__tests__/useMutationState.test-d.tsx index 795995aa440..a24b8763a6b 100644 --- a/packages/react-query/src/__tests__/useMutationState.test-d.tsx +++ b/packages/react-query/src/__tests__/useMutationState.test-d.tsx @@ -1,6 +1,10 @@ import { describe, expectTypeOf, it } from 'vitest' import { useMutationState } from '../useMutationState' -import type { MutationState, MutationStatus } from '@tanstack/query-core' +import type { + Mutation, + MutationState, + MutationStatus, +} from '@tanstack/query-core' describe('useMutationState', () => { it('should default to QueryState', () => { @@ -20,4 +24,19 @@ describe('useMutationState', () => { expectTypeOf(result).toEqualTypeOf>() }) + it('should propagate generics to select callback when TResult is typed MutationState', () => { + type MyData = { data: Array } + type MyError = { code: number; message: string } + type MyVars = { id: number } + + useMutationState>({ + filters: { mutationKey: ['key'] }, + select: (mutation) => { + expectTypeOf(mutation).toEqualTypeOf< + Mutation + >() + return mutation.state + }, + }) + }) }) diff --git a/packages/react-query/src/useMutationState.ts b/packages/react-query/src/useMutationState.ts index dfd0c41da3a..ce21870527b 100644 --- a/packages/react-query/src/useMutationState.ts +++ b/packages/react-query/src/useMutationState.ts @@ -22,25 +22,50 @@ export function useIsMutating( ).length } -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/solid-query/src/__tests__/useMutationState.test-d.tsx b/packages/solid-query/src/__tests__/useMutationState.test-d.tsx index dc8cca1e700..819bed9db70 100644 --- a/packages/solid-query/src/__tests__/useMutationState.test-d.tsx +++ b/packages/solid-query/src/__tests__/useMutationState.test-d.tsx @@ -1,6 +1,10 @@ import { describe, expectTypeOf, it } from 'vitest' import { useMutationState } from '../useMutationState' -import type { MutationState, MutationStatus } from '@tanstack/query-core' +import type { + Mutation, + MutationState, + MutationStatus, +} from '@tanstack/query-core' describe('useMutationState', () => { it('should default to QueryState', () => { @@ -18,4 +22,25 @@ describe('useMutationState', () => { expectTypeOf(result()).toEqualTypeOf>() }) + it('should propagate generics to select callback when TResult is typed MutationState', () => { + type MyData = { data: Array } + type MyError = { code: number; message: string } + type MyVars = { id: number } + + const result = useMutationState>( + () => ({ + filters: { mutationKey: ['key'] }, + select: (mutation) => { + expectTypeOf(mutation).toEqualTypeOf< + Mutation + >() + return mutation.state + }, + }), + ) + + expectTypeOf(result()).toEqualTypeOf< + Array> + >() + }) }) diff --git a/packages/solid-query/src/useMutationState.ts b/packages/solid-query/src/useMutationState.ts index 2a405a1ae4b..bc778ebb3e5 100644 --- a/packages/solid-query/src/useMutationState.ts +++ b/packages/solid-query/src/useMutationState.ts @@ -10,25 +10,50 @@ import type { import type { Accessor } from 'solid-js' import type { QueryClient } from './QueryClient' -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: Accessor> = () => ({}), +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: Accessor> = () => ({}), queryClient?: Accessor, ): Accessor> { const client = createMemo(() => useQueryClient(queryClient?.())) diff --git a/packages/svelte-query/src/types.ts b/packages/svelte-query/src/types.ts index 9a64d10dc3f..39cc83c805a 100644 --- a/packages/svelte-query/src/types.ts +++ b/packages/svelte-query/src/types.ts @@ -136,12 +136,25 @@ export type CreateMutationResult< TOnMutateResult = unknown, > = CreateBaseMutationResult +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + /** Options for useMutationState */ -export type MutationStateOptions = { +export type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: ( - mutation: Mutation, - ) => TResult + select?: (mutation: TMutation) => TResult } export type QueryClientProviderProps = { diff --git a/packages/svelte-query/src/useMutationState.svelte.ts b/packages/svelte-query/src/useMutationState.svelte.ts index c517e64b480..2932336bf0b 100644 --- a/packages/svelte-query/src/useMutationState.svelte.ts +++ b/packages/svelte-query/src/useMutationState.svelte.ts @@ -1,26 +1,48 @@ import { replaceEqualDeep } from '@tanstack/query-core' import { useQueryClient } from './useQueryClient.js' import type { + Mutation, MutationCache, MutationState, QueryClient, } from '@tanstack/query-core' import type { MutationStateOptions } from './types.js' -function getResult( +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/svelte-query/tests/useMutationState/SelectExample.svelte b/packages/svelte-query/tests/useMutationState/SelectExample.svelte index 683f68043eb..3c0389a7808 100644 --- a/packages/svelte-query/tests/useMutationState/SelectExample.svelte +++ b/packages/svelte-query/tests/useMutationState/SelectExample.svelte @@ -16,7 +16,7 @@ mutationStateOpts, }: { mutationOpts: Accessor - mutationStateOpts: MutationStateOptions + mutationStateOpts: MutationStateOptions } = $props() const queryClient = new QueryClient() diff --git a/packages/vue-query/src/useMutationState.ts b/packages/vue-query/src/useMutationState.ts index 3395b5fb743..5f55252cdca 100644 --- a/packages/vue-query/src/useMutationState.ts +++ b/packages/vue-query/src/useMutationState.ts @@ -48,27 +48,52 @@ export function useIsMutating( return length } -export type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +export type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( options: - | MutationStateOptions - | (() => MutationStateOptions) = {}, + | MutationStateOptions + | (() => MutationStateOptions) = {}, queryClient?: QueryClient, ): Readonly>> { const resolvedOptions = computed(() => {