Skip to content

Commit c2cf0ce

Browse files
feat(svelte-query): useMutationState (#7477)
* feat(svelte-query): createMutationState * Fix pnpm-lock * test: test cases for createMutationState for svelte-query * Undo changes to pnpm-lock * Export createMutationState * Rename to useMutationState * Fix eslint issues * Fix eslint/prettier --------- Co-authored-by: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com>
1 parent aadb831 commit c2cf0ce

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script lang="ts">
2+
import { QueryClient } from '@tanstack/query-core'
3+
import { setQueryClientContext } from '../context'
4+
import { createMutation } from '../createMutation'
5+
import { useMutationState } from '../useMutationState'
6+
7+
import type { CreateMutationOptions, MutationStateOptions } from '../types'
8+
9+
export let successMutationOpts: CreateMutationOptions
10+
export let errorMutationOpts: CreateMutationOptions
11+
export let mutationStateOpts: MutationStateOptions | undefined = undefined
12+
13+
const queryClient = new QueryClient()
14+
setQueryClientContext(queryClient)
15+
16+
const successMutation = createMutation(successMutationOpts)
17+
const errorMutation = createMutation(errorMutationOpts)
18+
19+
const mutationState = useMutationState(mutationStateOpts)
20+
$: statuses = $mutationState.map((state) => state.status)
21+
</script>
22+
23+
<div data-testid="result">
24+
{JSON.stringify(statuses)}
25+
</div>
26+
27+
<button data-testid="success" on:click={() => $successMutation.mutate()}>
28+
Click
29+
</button>
30+
<button data-testid="error" on:click={() => $errorMutation.mutate()}>
31+
Click
32+
</button>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { describe, expect, it, vi } from 'vitest'
2+
import { fireEvent, render, waitFor } from '@testing-library/svelte'
3+
import UseMutationState from './UseMutationState.svelte'
4+
5+
describe('useMutationState', () => {
6+
it('run few mutation functions and check from useMutationState ', async () => {
7+
const successMutationFn = vi.fn()
8+
9+
const errorMutationFn = vi.fn().mockImplementation(() => {
10+
throw 'error'
11+
})
12+
13+
const rendered = render(UseMutationState, {
14+
props: {
15+
successMutationOpts: {
16+
mutationKey: ['success'],
17+
mutationFn: successMutationFn,
18+
},
19+
20+
errorMutationOpts: {
21+
mutationKey: ['error'],
22+
mutationFn: errorMutationFn,
23+
},
24+
},
25+
})
26+
27+
fireEvent.click(rendered.getByTestId('success'))
28+
29+
await waitFor(() => {
30+
expect(successMutationFn).toHaveBeenCalledTimes(1)
31+
expect(rendered.getByTestId('result').innerHTML).toEqual('["success"]')
32+
})
33+
34+
fireEvent.click(rendered.getByTestId('error'))
35+
36+
await waitFor(() => {
37+
expect(errorMutationFn).toHaveBeenCalledTimes(1)
38+
expect(rendered.getByTestId('result').innerHTML).toEqual(
39+
'["success","error"]',
40+
)
41+
})
42+
})
43+
44+
it('can select specific type of mutation ( i.e: error only )', async () => {
45+
const successMutationFn = vi.fn()
46+
const errorMutationFn = vi.fn().mockImplementation(() => {
47+
throw 'error'
48+
})
49+
50+
const rendered = render(UseMutationState, {
51+
props: {
52+
successMutationOpts: {
53+
mutationKey: ['success'],
54+
mutationFn: successMutationFn,
55+
},
56+
57+
errorMutationOpts: {
58+
mutationKey: ['error'],
59+
mutationFn: errorMutationFn,
60+
},
61+
62+
mutationStateOpts: {
63+
filters: { status: 'error' },
64+
},
65+
},
66+
})
67+
68+
fireEvent.click(rendered.getByTestId('success'))
69+
70+
await waitFor(() => {
71+
expect(successMutationFn).toHaveBeenCalledTimes(1)
72+
expect(rendered.getByTestId('result').innerHTML).toEqual('[]')
73+
})
74+
75+
fireEvent.click(rendered.getByTestId('error'))
76+
77+
await waitFor(() => {
78+
expect(errorMutationFn).toHaveBeenCalledTimes(1)
79+
expect(rendered.getByTestId('result').innerHTML).toEqual('["error"]')
80+
})
81+
})
82+
83+
it('can select specific mutation using mutation key', async () => {
84+
const successMutationFn = vi.fn()
85+
const errorMutationFn = vi.fn().mockImplementation(() => {
86+
throw 'error'
87+
})
88+
89+
const rendered = render(UseMutationState, {
90+
props: {
91+
successMutationOpts: {
92+
mutationKey: ['success'],
93+
mutationFn: successMutationFn,
94+
},
95+
96+
errorMutationOpts: {
97+
mutationKey: ['error'],
98+
mutationFn: errorMutationFn,
99+
},
100+
101+
mutationStateOpts: {
102+
filters: { mutationKey: ['success'] },
103+
},
104+
},
105+
})
106+
107+
fireEvent.click(rendered.getByTestId('success'))
108+
109+
await waitFor(() => {
110+
expect(successMutationFn).toHaveBeenCalledTimes(1)
111+
expect(rendered.getByTestId('result').innerHTML).toEqual('["success"]')
112+
})
113+
114+
fireEvent.click(rendered.getByTestId('error'))
115+
116+
await waitFor(() => {
117+
expect(errorMutationFn).toHaveBeenCalledTimes(1)
118+
expect(rendered.getByTestId('result').innerHTML).toEqual('["success"]')
119+
})
120+
})
121+
})

packages/svelte-query/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export { createQueries } from './createQueries'
1818
export { createInfiniteQuery } from './createInfiniteQuery'
1919
export { infiniteQueryOptions } from './infiniteQueryOptions'
2020
export { createMutation } from './createMutation'
21+
export { useMutationState } from './useMutationState'
2122
export { useQueryClient } from './useQueryClient'
2223
export { useIsFetching } from './useIsFetching'
2324
export { useIsMutating } from './useIsMutating'

packages/svelte-query/src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import type {
44
InfiniteQueryObserverOptions,
55
InfiniteQueryObserverResult,
66
MutateFunction,
7+
Mutation,
8+
MutationFilters,
79
MutationObserverOptions,
810
MutationObserverResult,
11+
MutationState,
912
OmitKeyof,
1013
QueryKey,
1114
QueryObserverOptions,
@@ -134,3 +137,11 @@ type Override<TTargetA, TTargetB> = {
134137
? TTargetB[AKey]
135138
: TTargetA[AKey]
136139
}
140+
141+
/** Options for useMutationState */
142+
export type MutationStateOptions<TResult = MutationState> = {
143+
filters?: MutationFilters
144+
select?: (
145+
mutation: Mutation<unknown, DefaultError, unknown, unknown>,
146+
) => TResult
147+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { readable } from 'svelte/store'
2+
import { notifyManager, replaceEqualDeep } from '@tanstack/query-core'
3+
import { useQueryClient } from './useQueryClient'
4+
import type {
5+
MutationCache,
6+
MutationState,
7+
QueryClient,
8+
} from '@tanstack/query-core'
9+
import type { Readable } from 'svelte/store'
10+
import type { MutationStateOptions } from './types'
11+
12+
function getResult<TResult = MutationState>(
13+
mutationCache: MutationCache,
14+
options: MutationStateOptions<TResult>,
15+
): Array<TResult> {
16+
return mutationCache
17+
.findAll(options.filters)
18+
.map(
19+
(mutation): TResult =>
20+
(options.select ? options.select(mutation) : mutation.state) as TResult,
21+
)
22+
}
23+
24+
export function useMutationState<TResult = MutationState>(
25+
options: MutationStateOptions<TResult> = {},
26+
queryClient?: QueryClient,
27+
): Readable<Array<TResult>> {
28+
const client = useQueryClient(queryClient)
29+
const mutationCache = client.getMutationCache()
30+
31+
let result = getResult(mutationCache, options)
32+
33+
const { subscribe } = readable(result, (set) => {
34+
return mutationCache.subscribe(
35+
notifyManager.batchCalls(() => {
36+
const nextResult = replaceEqualDeep(
37+
result,
38+
getResult(mutationCache, options),
39+
)
40+
if (result !== nextResult) {
41+
result = nextResult
42+
set(result)
43+
}
44+
}),
45+
)
46+
})
47+
48+
return { subscribe }
49+
}

0 commit comments

Comments
 (0)