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
5 changes: 5 additions & 0 deletions .changeset/khaki-moons-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/svelte-query': patch
---

Fix `createQueries` staying inactive when it is initialized with an empty `queries` array.
19 changes: 19 additions & 0 deletions packages/svelte-query/src/containers.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,22 @@ export function createRawRef<T extends {} | Array<unknown>>(
): [T, (newValue: T) => void] {
const refObj = (Array.isArray(init) ? [] : {}) as T
const hiddenKeys = new SvelteSet<PropertyKey>()
// Absent keys have no `$state.raw` field to subscribe to, and `length` is a
// plain array property, so reads of either are tracked through this instead.
// Without it, anything observed while the ref is empty never re-runs.
let keyVersion = $state.raw(0)
const trackKeys = () => keyVersion
const out = new Proxy(refObj, {
get(target, prop, receiver) {
if (
hiddenKeys.has(prop) ||
!(prop in target) ||
(Array.isArray(target) && prop === 'length')
) {
trackKeys()
}
return Reflect.get(target, prop, receiver)
},
set(target, prop, value, receiver) {
hiddenKeys.delete(prop)
if (prop in target) {
Expand Down Expand Up @@ -88,6 +103,7 @@ export function createRawRef<T extends {} | Array<unknown>>(
const existingKeys = Object.keys(out)
const newKeys = Object.keys(newValue)
const keysToRemove = existingKeys.filter((key) => !newKeys.includes(key))
const keysAdded = newKeys.some((key) => !existingKeys.includes(key))
for (const key of keysToRemove) {
// @ts-expect-error
delete out[key]
Expand All @@ -100,6 +116,9 @@ export function createRawRef<T extends {} | Array<unknown>>(
// (See above)
out[key] = brand(() => newValue[key])
}
if (keysAdded || keysToRemove.length > 0) {
keyVersion++
}
}

// we can't pass `init` directly into the proxy because it'll never set the state fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { render } from '@testing-library/svelte'
import { queryKey, sleep } from '@tanstack/query-test-utils'
import { QueryClient, createQueries } from '../../src/index.js'
import { promiseWithResolvers, withEffectRoot } from '../utils.svelte.js'
import { promiseWithResolvers, ref, withEffectRoot } from '../utils.svelte.js'
import IsRestoring from './IsRestoring.svelte'
import type { CreateQueryResult } from '../../src/index.js'

Expand Down Expand Up @@ -108,6 +108,39 @@ describe('createQueries', () => {
}),
)

it(
'should track queries added to an initially empty array',
withEffectRoot(async () => {
const key1 = queryKey()
const queries = ref<
Array<{ queryKey: Array<string>; queryFn: () => Promise<string> }>
>([])
const results: Array<Array<CreateQueryResult>> = []

const result = createQueries(
() => ({ queries: queries.value }),
() => queryClient,
)

$effect(() => {
results.push(result.map((res) => ({ ...res })))
})

await vi.advanceTimersByTimeAsync(0)
expect(results).toMatchObject([[]])

queries.value = [
{ queryKey: key1, queryFn: () => sleep(10).then(() => 'data1') },
]

await vi.advanceTimersByTimeAsync(10)
expect(result[0]?.data).toBe('data1')
expect(results.at(-1)).toMatchObject([
{ status: 'success', data: 'data1' },
])
}),
)

it(
'should combine queries',
withEffectRoot(async () => {
Expand Down