From 22b08436113a8e23fb44d9574bff4434cb152100 Mon Sep 17 00:00:00 2001 From: Arno Wiest Date: Fri, 30 May 2025 14:11:06 +0200 Subject: [PATCH 1/3] fix(svelte-query): fix error when creating query outside of component initialization Add $effect.root to allow creating queries in event handlers. Previously creating a query outside of component initialization would throw an effect_orphan error --- .../src/createBaseQuery.svelte.ts | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/svelte-query/src/createBaseQuery.svelte.ts b/packages/svelte-query/src/createBaseQuery.svelte.ts index 8307f5e40f..c59c944b2b 100644 --- a/packages/svelte-query/src/createBaseQuery.svelte.ts +++ b/packages/svelte-query/src/createBaseQuery.svelte.ts @@ -57,26 +57,29 @@ export function createBaseQuery< createResult(), ) - $effect(() => { - const unsubscribe = isRestoring.current - ? () => undefined - : observer.subscribe(() => update(createResult())) - observer.updateResult() - return unsubscribe - }) - - $effect.pre(() => { - observer.setOptions(resolvedOptions) - // The only reason this is necessary is because of `isRestoring`. - // Because we don't subscribe while restoring, the following can occur: - // - `isRestoring` is true - // - `isRestoring` becomes false - // - `observer.subscribe` and `observer.updateResult` is called in the above effect, - // but the subsequent `fetch` has already completed - // - `result` misses the intermediate restored-but-not-fetched state - // - // this could technically be its own effect but that doesn't seem necessary - update(createResult()) + // Avoid effect_orphan error when creating a query inside an event handler instead of during component initialization + $effect.root(() => { + $effect(() => { + const unsubscribe = isRestoring.current + ? () => undefined + : observer.subscribe(() => update(createResult())) + observer.updateResult() + return unsubscribe + }) + + $effect.pre(() => { + observer.setOptions(resolvedOptions) + // The only reason this is necessary is because of `isRestoring`. + // Because we don't subscribe while restoring, the following can occur: + // - `isRestoring` is true + // - `isRestoring` becomes false + // - `observer.subscribe` and `observer.updateResult` is called in the above effect, + // but the subsequent `fetch` has already completed + // - `result` misses the intermediate restored-but-not-fetched state + // + // this could technically be its own effect but that doesn't seem necessary + update(createResult()) + }) }) return query From 84fe536e2022d5a06d4e1819a025606a209af751 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:12:56 +0000 Subject: [PATCH 2/3] ci: apply automated fixes --- packages/svelte-query/src/createBaseQuery.svelte.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte-query/src/createBaseQuery.svelte.ts b/packages/svelte-query/src/createBaseQuery.svelte.ts index c59c944b2b..90ee0337af 100644 --- a/packages/svelte-query/src/createBaseQuery.svelte.ts +++ b/packages/svelte-query/src/createBaseQuery.svelte.ts @@ -57,7 +57,7 @@ export function createBaseQuery< createResult(), ) - // Avoid effect_orphan error when creating a query inside an event handler instead of during component initialization + // Avoid effect_orphan error when creating a query inside an event handler instead of during component initialization $effect.root(() => { $effect(() => { const unsubscribe = isRestoring.current @@ -66,7 +66,7 @@ export function createBaseQuery< observer.updateResult() return unsubscribe }) - + $effect.pre(() => { observer.setOptions(resolvedOptions) // The only reason this is necessary is because of `isRestoring`. From 383aaa5075e28e8ae188426356aa09224b1f35b5 Mon Sep 17 00:00:00 2001 From: Arno Date: Fri, 26 Sep 2025 12:51:39 +0200 Subject: [PATCH 3/3] fix: add test without effect root --- .../tests/createQuery.svelte.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/svelte-query/tests/createQuery.svelte.test.ts b/packages/svelte-query/tests/createQuery.svelte.test.ts index 56dfac5fc9..61e2a2679f 100644 --- a/packages/svelte-query/tests/createQuery.svelte.test.ts +++ b/packages/svelte-query/tests/createQuery.svelte.test.ts @@ -1892,4 +1892,91 @@ describe('createQuery', () => { expect(query.error?.message).toBe('Local Error') }), ) + + it('should return the correct states for a successful query when running without effect root', async () => { + const { promise, resolve } = promiseWithResolvers() + + const query = createQuery( + () => ({ + queryKey: ['test'], + queryFn: () => promise, + }), + () => queryClient, + ) + + if (query.isPending) { + expectTypeOf(query.data).toEqualTypeOf() + expectTypeOf(query.error).toEqualTypeOf() + } else if (query.isLoadingError) { + expectTypeOf(query.data).toEqualTypeOf() + expectTypeOf(query.error).toEqualTypeOf() + } else { + expectTypeOf(query.data).toEqualTypeOf() + expectTypeOf(query.error).toEqualTypeOf() + } + + const promise1 = query.promise + + expect(query).toEqual({ + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isEnabled: true, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: true, + isPaused: false, + isPending: true, + isInitialLoading: true, + isLoading: true, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: false, + refetch: expect.any(Function), + status: 'pending', + fetchStatus: 'fetching', + promise: expect.any(Promise), + }) + resolve('resolved') + await vi.waitFor(() => + expect(query).toEqual({ + data: 'resolved', + dataUpdatedAt: expect.any(Number), + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isEnabled: true, + isError: false, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isPaused: false, + isPending: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: true, + refetch: expect.any(Function), + status: 'success', + fetchStatus: 'idle', + promise: expect.any(Promise), + }), + ) + + expect(promise1).toBe(query.promise) + }) })