Skip to content

Commit 464ab2e

Browse files
fix: prevent infinite loop when calling pushState/replaceState in effect (#13914)
* fix: prevent infinite loop when calling `pushState`/`replaceState` in effect * oops * fix duplicate import * test for shallow navigation with effects * remove todo comment --------- Co-authored-by: Chew Tee Ming <[email protected]>
1 parent abcb7c0 commit 464ab2e

File tree

6 files changed

+48
-6
lines changed

6 files changed

+48
-6
lines changed

.changeset/chatty-pears-notice.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: prevent infinite loop when calling `pushState`/`replaceState` in `$effect`

packages/kit/src/runtime/client/client.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BROWSER, DEV } from 'esm-env';
2-
import { onMount, tick } from 'svelte';
2+
import { onMount, tick, untrack } from 'svelte';
33
import {
44
decode_params,
55
decode_pathname,
@@ -2111,7 +2111,7 @@ export function pushState(url, state) {
21112111
page.state = state;
21122112
root.$set({
21132113
// we need to assign a new page object so that subscribers are correctly notified
2114-
page: clone_page(page)
2114+
page: untrack(() => clone_page(page))
21152115
});
21162116

21172117
clear_onward_history(current_history_index, current_navigation_index);
@@ -2154,7 +2154,7 @@ export function replaceState(url, state) {
21542154

21552155
page.state = state;
21562156
root.$set({
2157-
page: clone_page(page)
2157+
page: untrack(() => clone_page(page))
21582158
});
21592159
}
21602160

packages/kit/test/apps/basics/src/app.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ declare global {
1010
}
1111

1212
interface PageState {
13-
active: boolean;
13+
active?: boolean;
14+
count?: number;
1415
}
15-
16-
interface Platform {}
1716
}
1817
}
1918

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script lang="ts">
2+
import { pushState } from '$app/navigation';
3+
4+
let count = $state(0);
5+
6+
$effect(() => {
7+
if (count) pushState('', { count });
8+
});
9+
</script>
10+
11+
<p>count: {count}</p>
12+
<button onclick={() => count++}>Increment</button>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script lang="ts">
2+
import { pushState as replaceState } from '$app/navigation';
3+
4+
let count = $state(0);
5+
6+
$effect(() => {
7+
if (count) replaceState('', { count });
8+
});
9+
</script>
10+
11+
<p>count: {count}</p>
12+
<button onclick={() => count++}>Increment</button>

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,20 @@ test.describe('Shallow routing', () => {
14771477
await expect(page.locator('h1')).toHaveText('parent');
14781478
await expect(page.locator('p')).toHaveText('active: true');
14791479
});
1480+
1481+
test('pushState does not loop infinitely in $effect', async ({ page }) => {
1482+
await page.goto('/shallow-routing/push-state/effect');
1483+
await expect(page.locator('p')).toHaveText('count: 0');
1484+
await page.locator('button').click();
1485+
await expect(page.locator('p')).toHaveText('count: 1');
1486+
});
1487+
1488+
test('replaceState does not loop infinitely in $effect', async ({ page }) => {
1489+
await page.goto('/shallow-routing/replace-state/effect');
1490+
await expect(page.locator('p')).toHaveText('count: 0');
1491+
await page.locator('button').click();
1492+
await expect(page.locator('p')).toHaveText('count: 1');
1493+
});
14801494
});
14811495

14821496
test.describe('reroute', () => {

0 commit comments

Comments
 (0)