Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-mutation-state-generics.md
Original file line number Diff line number Diff line change
@@ -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
21 changes: 20 additions & 1 deletion packages/preact-query/src/__tests__/useMutationState.test-d.tsx
Original file line number Diff line number Diff line change
@@ -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'
Comment on lines +1 to 8
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix import order to satisfy ESLint.

import/order currently expects the @tanstack/query-core type import to come after ../useMutationState.

Suggested fix
-import type {
-  Mutation,
-  MutationState,
-  MutationStatus,
-} from '@tanstack/query-core'
 import { describe, expectTypeOf, it } from 'vitest'
 
 import { useMutationState } from '../useMutationState'
+import type {
+  Mutation,
+  MutationState,
+  MutationStatus,
+} from '@tanstack/query-core'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type {
Mutation,
MutationState,
MutationStatus,
} from '@tanstack/query-core'
import { describe, expectTypeOf, it } from 'vitest'
import { useMutationState } from '../useMutationState'
import { describe, expectTypeOf, it } from 'vitest'
import { useMutationState } from '../useMutationState'
import type {
Mutation,
MutationState,
MutationStatus,
} from '@tanstack/query-core'
🧰 Tools
🪛 ESLint

[error] 1-5: @tanstack/query-core type import should occur after import of ../useMutationState

(import/order)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/preact-query/src/__tests__/useMutationState.test-d.tsx` around lines
1 - 8, ESLint import/order wants the local module import before external package
type imports; reorder the imports so the relative import of useMutationState
comes before the type-only import from `@tanstack/query-core` (i.e., move "import
{ useMutationState } from '../useMutationState'" above the "import type {
Mutation, MutationState, MutationStatus } from '@tanstack/query-core'").

Expand All @@ -21,4 +25,19 @@ describe('useMutationState', () => {

expectTypeOf(result).toEqualTypeOf<Array<MutationStatus>>()
})
it('should propagate generics to select callback when TResult is typed MutationState', () => {
type MyData = { data: Array<string> }
type MyError = { code: number; message: string }
type MyVars = { id: number }

useMutationState<MutationState<MyData, MyError, MyVars>>({
filters: { mutationKey: ['key'] },
select: (mutation) => {
expectTypeOf(mutation).toEqualTypeOf<
Mutation<MyData, MyError, MyVars, unknown>
>()
return mutation.state
},
})
})
})
41 changes: 34 additions & 7 deletions packages/preact-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,52 @@ export function useIsMutating(
).length
}

type MutationStateOptions<TResult = MutationState> = {
type MutationTypeFromResult<TResult> = [TResult] extends [
MutationState<
infer TData,
infer TError,
infer TVariables,
infer TOnMutateResult
>,
]
? Mutation<TData, TError, TVariables, TOnMutateResult>
: Mutation

type MutationStateOptions<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
> = {
filters?: MutationFilters
select?: (mutation: Mutation) => TResult
select?: (mutation: TMutation) => TResult
}

function getResult<TResult = MutationState>(
function getResult<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>,
options: MutationStateOptions<TResult, TMutation>,
): Array<TResult> {
return mutationCache
.findAll(options.filters)
.map(
(mutation): TResult =>
(options.select ? options.select(mutation) : mutation.state) as TResult,
(options.select
? (options.select as unknown as (mutation: Mutation) => TResult)(
mutation,
)
: mutation.state) as TResult,
)
}

export function useMutationState<TResult = MutationState>(
options: MutationStateOptions<TResult> = {},
export function useMutationState<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
options: MutationStateOptions<TResult, TMutation> = {},
queryClient?: QueryClient,
): Array<TResult> {
const mutationCache = useQueryClient(queryClient).getMutationCache()
Expand Down
21 changes: 20 additions & 1 deletion packages/react-query/src/__tests__/useMutationState.test-d.tsx
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -20,4 +24,19 @@ describe('useMutationState', () => {

expectTypeOf(result).toEqualTypeOf<Array<MutationStatus>>()
})
it('should propagate generics to select callback when TResult is typed MutationState', () => {
type MyData = { data: Array<string> }
type MyError = { code: number; message: string }
type MyVars = { id: number }

useMutationState<MutationState<MyData, MyError, MyVars>>({
filters: { mutationKey: ['key'] },
select: (mutation) => {
expectTypeOf(mutation).toEqualTypeOf<
Mutation<MyData, MyError, MyVars, unknown>
>()
return mutation.state
},
})
})
})
41 changes: 34 additions & 7 deletions packages/react-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,52 @@ export function useIsMutating(
).length
}

type MutationStateOptions<TResult = MutationState> = {
type MutationTypeFromResult<TResult> = [TResult] extends [
MutationState<
infer TData,
infer TError,
infer TVariables,
infer TOnMutateResult
>,
]
? Mutation<TData, TError, TVariables, TOnMutateResult>
: Mutation

type MutationStateOptions<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
> = {
filters?: MutationFilters
select?: (mutation: Mutation) => TResult
select?: (mutation: TMutation) => TResult
}

function getResult<TResult = MutationState>(
function getResult<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>,
options: MutationStateOptions<TResult, TMutation>,
): Array<TResult> {
return mutationCache
.findAll(options.filters)
.map(
(mutation): TResult =>
(options.select ? options.select(mutation) : mutation.state) as TResult,
(options.select
? (options.select as unknown as (mutation: Mutation) => TResult)(
mutation,
)
: mutation.state) as TResult,
)
}

export function useMutationState<TResult = MutationState>(
options: MutationStateOptions<TResult> = {},
export function useMutationState<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
options: MutationStateOptions<TResult, TMutation> = {},
queryClient?: QueryClient,
): Array<TResult> {
const mutationCache = useQueryClient(queryClient).getMutationCache()
Expand Down
27 changes: 26 additions & 1 deletion packages/solid-query/src/__tests__/useMutationState.test-d.tsx
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -18,4 +22,25 @@ describe('useMutationState', () => {

expectTypeOf(result()).toEqualTypeOf<Array<MutationStatus>>()
})
it('should propagate generics to select callback when TResult is typed MutationState', () => {
type MyData = { data: Array<string> }
type MyError = { code: number; message: string }
type MyVars = { id: number }

const result = useMutationState<MutationState<MyData, MyError, MyVars>>(
() => ({
filters: { mutationKey: ['key'] },
select: (mutation) => {
expectTypeOf(mutation).toEqualTypeOf<
Mutation<MyData, MyError, MyVars, unknown>
>()
return mutation.state
},
}),
)

expectTypeOf(result()).toEqualTypeOf<
Array<MutationState<MyData, MyError, MyVars, unknown>>
>()
})
})
41 changes: 34 additions & 7 deletions packages/solid-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,52 @@ import type {
import type { Accessor } from 'solid-js'
import type { QueryClient } from './QueryClient'

type MutationStateOptions<TResult = MutationState> = {
type MutationTypeFromResult<TResult> = [TResult] extends [
MutationState<
infer TData,
infer TError,
infer TVariables,
infer TOnMutateResult
>,
]
? Mutation<TData, TError, TVariables, TOnMutateResult>
: Mutation

type MutationStateOptions<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
> = {
filters?: MutationFilters
select?: (mutation: Mutation) => TResult
select?: (mutation: TMutation) => TResult
}

function getResult<TResult = MutationState>(
function getResult<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>,
options: MutationStateOptions<TResult, TMutation>,
): Array<TResult> {
return mutationCache
.findAll(options.filters)
.map(
(mutation): TResult =>
(options.select ? options.select(mutation) : mutation.state) as TResult,
(options.select
? (options.select as unknown as (mutation: Mutation) => TResult)(
mutation,
)
: mutation.state) as TResult,
)
}

export function useMutationState<TResult = MutationState>(
options: Accessor<MutationStateOptions<TResult>> = () => ({}),
export function useMutationState<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
options: Accessor<MutationStateOptions<TResult, TMutation>> = () => ({}),
queryClient?: Accessor<QueryClient>,
): Accessor<Array<TResult>> {
const client = createMemo(() => useQueryClient(queryClient?.()))
Expand Down
21 changes: 17 additions & 4 deletions packages/svelte-query/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,25 @@ export type CreateMutationResult<
TOnMutateResult = unknown,
> = CreateBaseMutationResult<TData, TError, TVariables, TOnMutateResult>

type MutationTypeFromResult<TResult> = [TResult] extends [
MutationState<
infer TData,
infer TError,
infer TVariables,
infer TOnMutateResult
>,
]
? Mutation<TData, TError, TVariables, TOnMutateResult>
: Mutation<unknown, DefaultError, unknown, unknown>

/** Options for useMutationState */
export type MutationStateOptions<TResult = MutationState> = {
export type MutationStateOptions<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
> = {
filters?: MutationFilters
select?: (
mutation: Mutation<unknown, DefaultError, unknown, unknown>,
) => TResult
select?: (mutation: TMutation) => TResult
}

export type QueryClientProviderProps = {
Expand Down
34 changes: 29 additions & 5 deletions packages/svelte-query/src/useMutationState.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
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<TResult = MutationState>(
type MutationTypeFromResult<TResult> = [TResult] extends [
MutationState<
infer TData,
infer TError,
infer TVariables,
infer TOnMutateResult
>,
]
? Mutation<TData, TError, TVariables, TOnMutateResult>
: Mutation

function getResult<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>,
options: MutationStateOptions<TResult, TMutation>,
): Array<TResult> {
return mutationCache
.findAll(options.filters)
.map(
(mutation): TResult =>
(options.select ? options.select(mutation) : mutation.state) as TResult,
(options.select
? (options.select as unknown as (mutation: Mutation) => TResult)(
mutation,
)
: mutation.state) as TResult,
)
}

export function useMutationState<TResult = MutationState>(
options: MutationStateOptions<TResult> = {},
export function useMutationState<
TResult = MutationState,
TMutation extends Mutation<any, any, any, any> =
MutationTypeFromResult<TResult>,
>(
options: MutationStateOptions<TResult, TMutation> = {},
queryClient?: QueryClient,
): Array<TResult> {
const mutationCache = useQueryClient(queryClient).getMutationCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
mutationStateOpts,
}: {
mutationOpts: Accessor<CreateMutationOptions>
mutationStateOpts: MutationStateOptions<any>
mutationStateOpts: MutationStateOptions<any, any>
} = $props()

const queryClient = new QueryClient()
Expand Down
Loading
Loading