Skip to content

Commit dd04604

Browse files
committed
fix(svelte-query): update observers when passed in query client changes
1 parent 9b518cf commit dd04604

File tree

4 files changed

+171
-47
lines changed

4 files changed

+171
-47
lines changed

packages/svelte-query/src/createBaseQuery.svelte.ts

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
CreateBaseQueryOptions,
99
CreateBaseQueryResult,
1010
} from './types.js'
11+
import { watchChanges } from './utils.svelte.js'
1112

1213
/**
1314
* Base implementation for `createQuery` and `createInfiniteQuery`
@@ -39,15 +40,25 @@ export function createBaseQuery<
3940
})
4041

4142
/** Creates the observer */
42-
const observer = new Observer<
43-
TQueryFnData,
44-
TError,
45-
TData,
46-
TQueryData,
47-
TQueryKey
48-
>(
49-
client,
50-
untrack(() => resolvedOptions),
43+
// svelte-ignore state_referenced_locally - intentional, initial value
44+
let observer = $state(
45+
new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(
46+
client,
47+
resolvedOptions,
48+
),
49+
)
50+
watchChanges(
51+
() => client,
52+
'pre',
53+
() => {
54+
observer = new Observer<
55+
TQueryFnData,
56+
TError,
57+
TData,
58+
TQueryData,
59+
TQueryKey
60+
>(client, resolvedOptions)
61+
},
5162
)
5263

5364
function createResult() {
@@ -69,19 +80,29 @@ export function createBaseQuery<
6980
return unsubscribe
7081
})
7182

72-
$effect.pre(() => {
73-
observer.setOptions(resolvedOptions)
74-
// The only reason this is necessary is because of `isRestoring`.
75-
// Because we don't subscribe while restoring, the following can occur:
76-
// - `isRestoring` is true
77-
// - `isRestoring` becomes false
78-
// - `observer.subscribe` and `observer.updateResult` is called in the above effect,
79-
// but the subsequent `fetch` has already completed
80-
// - `result` misses the intermediate restored-but-not-fetched state
81-
//
82-
// this could technically be its own effect but that doesn't seem necessary
83-
update(createResult())
84-
})
83+
watchChanges(
84+
() => resolvedOptions,
85+
'pre',
86+
() => {
87+
observer.setOptions(resolvedOptions)
88+
},
89+
)
90+
watchChanges(
91+
() => [resolvedOptions, observer],
92+
'pre',
93+
() => {
94+
// The only reason this is necessary is because of `isRestoring`.
95+
// Because we don't subscribe while restoring, the following can occur:
96+
// - `isRestoring` is true
97+
// - `isRestoring` becomes false
98+
// - `observer.subscribe` and `observer.updateResult` is called in the above effect,
99+
// but the subsequent `fetch` has already completed
100+
// - `result` misses the intermediate restored-but-not-fetched state
101+
//
102+
// this could technically be its own effect but that doesn't seem necessary
103+
update(createResult())
104+
},
105+
)
85106

86107
return query
87108
}
Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { onDestroy } from 'svelte'
1+
import { onDestroy, untrack } from 'svelte'
22

33
import { MutationObserver, notifyManager } from '@tanstack/query-core'
44
import { useQueryClient } from './useQueryClient.js'
@@ -8,6 +8,7 @@ import type {
88
CreateMutationOptions,
99
CreateMutationResult,
1010
} from './types.js'
11+
import { watchChanges } from './utils.svelte.js'
1112

1213
import type { DefaultError, QueryClient } from '@tanstack/query-core'
1314

@@ -24,49 +25,80 @@ export function createMutation<
2425
options: Accessor<CreateMutationOptions<TData, TError, TVariables, TContext>>,
2526
queryClient?: Accessor<QueryClient>,
2627
): CreateMutationResult<TData, TError, TVariables, TContext> {
27-
const client = useQueryClient(queryClient?.())
28+
const client = $derived(useQueryClient(queryClient?.()))
2829

29-
const observer = new MutationObserver<TData, TError, TVariables, TContext>(
30-
client,
31-
options(),
30+
// svelte-ignore state_referenced_locally - intentional, initial value
31+
let observer = $state(
32+
// svelte-ignore state_referenced_locally - intentional, initial value
33+
new MutationObserver<TData, TError, TVariables, TContext>(
34+
client,
35+
options(),
36+
),
3237
)
3338

39+
watchChanges(
40+
() => client,
41+
'pre',
42+
() => {
43+
observer = new MutationObserver(client, options())
44+
},
45+
)
46+
47+
$effect.pre(() => {
48+
observer.setOptions(options())
49+
})
50+
3451
const mutate = <CreateMutateFunction<TData, TError, TVariables, TContext>>((
3552
variables,
3653
mutateOptions,
3754
) => {
3855
observer.mutate(variables, mutateOptions).catch(noop)
3956
})
4057

41-
$effect.pre(() => {
42-
observer.setOptions(options())
43-
})
58+
let result = $state(observer.getCurrentResult())
59+
watchChanges(
60+
() => observer,
61+
'pre',
62+
() => {
63+
result = observer.getCurrentResult()
64+
},
65+
)
4466

45-
const result = observer.getCurrentResult()
67+
const subscribe = (
68+
observer: MutationObserver<TData, TError, TVariables, TContext>,
69+
) =>
70+
observer.subscribe((val) => {
71+
notifyManager.batchCalls(() => {
72+
Object.assign(result, val)
73+
})()
74+
})
75+
let unsubscribe = $state(subscribe(observer))
4676

47-
const unsubscribe = observer.subscribe((val) => {
48-
notifyManager.batchCalls(() => {
49-
Object.assign(result, val)
50-
})()
77+
$effect.pre(() => {
78+
unsubscribe = subscribe(observer)
5179
})
5280

5381
onDestroy(() => {
5482
unsubscribe()
5583
})
5684

85+
const resultProxy = $derived(
86+
new Proxy(result, {
87+
get: (_, prop) => {
88+
const r = {
89+
...result,
90+
mutate,
91+
mutateAsync: result.mutate,
92+
}
93+
if (prop == 'value') return r
94+
// @ts-expect-error
95+
return r[prop]
96+
},
97+
}),
98+
)
99+
57100
// @ts-expect-error
58-
return new Proxy(result, {
59-
get: (_, prop) => {
60-
const r = {
61-
...result,
62-
mutate,
63-
mutateAsync: result.mutate,
64-
}
65-
if (prop == 'value') return r
66-
// @ts-expect-error
67-
return r[prop]
68-
},
69-
})
101+
return resultProxy
70102
}
71103

72104
function noop() {}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { untrack } from 'svelte'
2+
// modified from the great https://github.com/svecosystem/runed
3+
function runEffect(
4+
flush: 'post' | 'pre',
5+
effect: () => void | VoidFunction,
6+
): void {
7+
switch (flush) {
8+
case 'post':
9+
$effect(effect)
10+
break
11+
case 'pre':
12+
$effect.pre(effect)
13+
break
14+
}
15+
}
16+
type Getter<T> = () => T
17+
export const watchChanges = <T>(
18+
sources: Getter<T> | Array<Getter<T>>,
19+
flush: 'post' | 'pre',
20+
effect: (
21+
values: T | Array<T>,
22+
previousValues: T | undefined | Array<T | undefined>,
23+
) => void,
24+
) => {
25+
let active = false
26+
let previousValues: T | undefined | Array<T | undefined> = Array.isArray(
27+
sources,
28+
)
29+
? []
30+
: undefined
31+
runEffect(flush, () => {
32+
const values = Array.isArray(sources)
33+
? sources.map((source) => source())
34+
: sources()
35+
if (!active) {
36+
active = true
37+
previousValues = values
38+
return
39+
}
40+
const cleanup = untrack(() => effect(values, previousValues))
41+
previousValues = values
42+
return cleanup
43+
})
44+
}

packages/svelte-query/tests/createQuery.svelte.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,4 +1885,31 @@ describe('createQuery', () => {
18851885
expect(query.error?.message).toBe('Local Error')
18861886
}),
18871887
)
1888+
1889+
it(
1890+
'should support changing provided query client',
1891+
withEffectRoot(async () => {
1892+
const queryClient1 = new QueryClient()
1893+
const queryClient2 = new QueryClient()
1894+
1895+
let queryClient = $state(queryClient1)
1896+
1897+
const key = ['test']
1898+
1899+
createQuery(
1900+
() => ({
1901+
queryKey: key,
1902+
queryFn: () => Promise.resolve('prefetched'),
1903+
}),
1904+
() => queryClient,
1905+
)
1906+
1907+
expect(queryClient1.getQueryCache().find({ queryKey: key })).toBeDefined()
1908+
1909+
queryClient = queryClient2
1910+
flushSync()
1911+
1912+
expect(queryClient2.getQueryCache().find({ queryKey: key })).toBeDefined()
1913+
}),
1914+
)
18881915
})

0 commit comments

Comments
 (0)