diff --git a/.changeset/chatty-pears-notice.md b/.changeset/chatty-pears-notice.md new file mode 100644 index 000000000000..7781f2b07ab1 --- /dev/null +++ b/.changeset/chatty-pears-notice.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: prevent infinite loop when calling `pushState`/`replaceState` in `$effect` diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 4b8eb5831e4a..84beeb95d79a 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1,5 +1,5 @@ import { BROWSER, DEV } from 'esm-env'; -import { onMount, tick } from 'svelte'; +import { onMount, tick, untrack } from 'svelte'; import { decode_params, decode_pathname, @@ -2111,7 +2111,7 @@ export function pushState(url, state) { page.state = state; root.$set({ // we need to assign a new page object so that subscribers are correctly notified - page: clone_page(page) + page: untrack(() => clone_page(page)) }); clear_onward_history(current_history_index, current_navigation_index); @@ -2154,7 +2154,7 @@ export function replaceState(url, state) { page.state = state; root.$set({ - page: clone_page(page) + page: untrack(() => clone_page(page)) }); } diff --git a/packages/kit/test/apps/basics/src/app.d.ts b/packages/kit/test/apps/basics/src/app.d.ts index f40b6d8c868a..cbfdcaafa47a 100644 --- a/packages/kit/test/apps/basics/src/app.d.ts +++ b/packages/kit/test/apps/basics/src/app.d.ts @@ -10,10 +10,9 @@ declare global { } interface PageState { - active: boolean; + active?: boolean; + count?: number; } - - interface Platform {} } } diff --git a/packages/kit/test/apps/basics/src/routes/shallow-routing/push-state/effect/+page.svelte b/packages/kit/test/apps/basics/src/routes/shallow-routing/push-state/effect/+page.svelte new file mode 100644 index 000000000000..0bc4747beb5f --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shallow-routing/push-state/effect/+page.svelte @@ -0,0 +1,12 @@ + + +
count: {count}
+ diff --git a/packages/kit/test/apps/basics/src/routes/shallow-routing/replace-state/effect/+page.svelte b/packages/kit/test/apps/basics/src/routes/shallow-routing/replace-state/effect/+page.svelte new file mode 100644 index 000000000000..cf298aac8150 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/shallow-routing/replace-state/effect/+page.svelte @@ -0,0 +1,12 @@ + + +count: {count}
+ diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index 63ad753637ec..8e494bb82d0c 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -1477,6 +1477,20 @@ test.describe('Shallow routing', () => { await expect(page.locator('h1')).toHaveText('parent'); await expect(page.locator('p')).toHaveText('active: true'); }); + + test('pushState does not loop infinitely in $effect', async ({ page }) => { + await page.goto('/shallow-routing/push-state/effect'); + await expect(page.locator('p')).toHaveText('count: 0'); + await page.locator('button').click(); + await expect(page.locator('p')).toHaveText('count: 1'); + }); + + test('replaceState does not loop infinitely in $effect', async ({ page }) => { + await page.goto('/shallow-routing/replace-state/effect'); + await expect(page.locator('p')).toHaveText('count: 0'); + await page.locator('button').click(); + await expect(page.locator('p')).toHaveText('count: 1'); + }); }); test.describe('reroute', () => {