Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
},
})
})
})
39 changes: 32 additions & 7 deletions packages/preact-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,50 @@ 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(mutation as TMutation)
: 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
},
})
})
})
39 changes: 32 additions & 7 deletions packages/react-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,50 @@ 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(mutation as TMutation)
: 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>>
>()
})
})
39 changes: 32 additions & 7 deletions packages/solid-query/src/useMutationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,50 @@ 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(mutation as TMutation)
: 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
32 changes: 27 additions & 5 deletions packages/svelte-query/src/useMutationState.svelte.ts
Original file line number Diff line number Diff line change
@@ -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<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(mutation as TMutation)
: 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