diff --git a/.changeset/lit-mutation-state-generics.md b/.changeset/lit-mutation-state-generics.md new file mode 100644 index 00000000000..95d828c3be7 --- /dev/null +++ b/.changeset/lit-mutation-state-generics.md @@ -0,0 +1,5 @@ +--- +'@tanstack/lit-query': patch +--- + +fix(types): propagate generic type parameters to the `useMutationState` select callback diff --git a/packages/lit-query/src/__tests__/mutation-state-types.test.ts b/packages/lit-query/src/__tests__/mutation-state-types.test.ts new file mode 100644 index 00000000000..3b44d9dfeb9 --- /dev/null +++ b/packages/lit-query/src/__tests__/mutation-state-types.test.ts @@ -0,0 +1,79 @@ +import { QueryClient } from '@tanstack/query-core' +import { LitElement } from 'lit' +import { describe, expectTypeOf, it } from 'vitest' +import { useMutationState } from '../useMutationState.js' +import { generateElementName } from './utils.js' +import type { + Mutation, + MutationState, + MutationStatus, +} from '@tanstack/query-core' + +describe('useMutationState types', () => { + it('defaults to mutation state with the default error type', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { filters: { status: 'pending' } }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf< + Array> + >() + }) + + it('infers the selected status', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { + filters: { status: 'pending' }, + select: (mutation) => mutation.state.status, + }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf>() + }) + + it('allows selecting a default error message', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { select: (mutation) => mutation.state.error?.message }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf>() + }) + + it('propagates typed mutation state generics to the select callback', () => { + type MyData = { data: Array } + type MyError = { code: number; message: string } + type MyVariables = { id: number } + + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + useMutationState>( + new Host(), + { + filters: { mutationKey: ['key'] }, + select: (mutation) => { + expectTypeOf(mutation).toEqualTypeOf< + Mutation + >() + return mutation.state + }, + }, + new QueryClient(), + ) + }) +}) diff --git a/packages/lit-query/src/useMutationState.ts b/packages/lit-query/src/useMutationState.ts index f60b9dd0c30..4872be82ccf 100644 --- a/packages/lit-query/src/useMutationState.ts +++ b/packages/lit-query/src/useMutationState.ts @@ -10,14 +10,28 @@ import type { } from '@tanstack/query-core' import type { ReactiveControllerHost } from 'lit' -/** - * Options accepted by `useMutationState`. - */ -export type MutationStateOptions = { +/** Recovers mutation generics when the selected result is a mutation state. */ +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +/** Options accepted by `useMutationState`. */ +export type MutationStateOptions< + TResult, + TMutation extends Mutation = + MutationTypeFromResult, +> = { /** Filters used to select mutations from the mutation cache. */ filters?: Accessor /** Maps each matching mutation to the value returned by the accessor. */ - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } /** @@ -31,13 +45,16 @@ export type MutationStateAccessor = ValueAccessor> & { destroy: () => void } -class MutationStateController extends BaseController> { +class MutationStateController< + TResult, + TMutation extends Mutation, +> extends BaseController> { private queryClient: QueryClient | undefined private unsubscribe: (() => void) | undefined constructor( host: ReactiveControllerHost, - private readonly options: MutationStateOptions, + private readonly options: MutationStateOptions, queryClient?: QueryClient, ) { super(host, [], queryClient) @@ -144,7 +161,7 @@ class MutationStateController extends BaseController> { return mutations.map((mutation) => { if (select) { - return select(mutation) + return select(mutation as TMutation) } return mutation.state as TResult @@ -184,13 +201,19 @@ class MutationStateController extends BaseController> { * ``` */ export function useMutationState< - TResult = MutationState, + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, >( host: ReactiveControllerHost, - options: MutationStateOptions = {}, + options: MutationStateOptions = {}, queryClient?: QueryClient, ): MutationStateAccessor { - const controller = new MutationStateController(host, options, queryClient) + const controller = new MutationStateController( + host, + options, + queryClient, + ) return Object.assign( createValueAccessor(() => controller.current), {