diff --git a/playground/src/routes/paramOnNavigateBug/+page.svelte b/playground/src/routes/paramOnNavigateBug/+page.svelte new file mode 100644 index 0000000..1c00224 --- /dev/null +++ b/playground/src/routes/paramOnNavigateBug/+page.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/sveltekit-search-params.ts b/src/lib/sveltekit-search-params.ts index e3e7bf1..0945345 100644 --- a/src/lib/sveltekit-search-params.ts +++ b/src/lib/sveltekit-search-params.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { browser, building } from '$app/environment'; import { goto } from '$app/navigation'; -import { page as page_store } from '$app/stores'; +import { navigating, page as page_store } from '$app/stores'; import type { Page } from '@sveltejs/kit'; import { derived, @@ -11,6 +11,8 @@ import { type Updater, type Writable, type Readable, + type Subscriber, + type Unsubscriber, readable, } from 'svelte/store'; import { @@ -326,8 +328,23 @@ export function queryParam( const override = writable(null); let firstTime = true; let currentValue: T | null; + + let isNavigating = false; + function _set(value: T | null, changeImmediately?: boolean) { if (!browser) return; + + // Wait for previous navigation to be finished before updating again + if (isNavigating) { + const unsubscribe = navigating.subscribe((nav) => { + if (nav?.type !== 'goto') { + _set(value, changeImmediately); + unsubscribe(); + } + }); + return; + } + firstTime = false; const hash = window.location.hash; const toBatch = (query: URLSearchParams) => { @@ -376,10 +393,10 @@ export function queryParam( }); } - const { subscribe } = derived<[typeof page, typeof override], T | null>( + const store = derived<[typeof page, typeof override], T | null>( [page, override], ([$page, $override], set) => { - if ($override) { + if ($override != undefined) { if (isComplexEqual(currentValue, $override, equalityFn)) { return; } @@ -409,10 +426,29 @@ export function queryParam( set(newValue) { _set(newValue); }, - subscribe, update: (updater: Updater) => { const newValue = updater(currentValue); _set(newValue); }, + subscribe( + run: Subscriber, + invalidate?: (value?: T | null) => void, + ): Unsubscriber { + // Subscribe to the derived store + const storeUnsubscribe = store.subscribe(run, invalidate); + // Subscribe to isNavigating + let unsubscribeNavigating: () => void; + if (browser) { + unsubscribeNavigating = navigating.subscribe((nav) => { + isNavigating = nav?.type === 'goto'; + }); + } + return () => { + storeUnsubscribe(); + if (unsubscribeNavigating) { + unsubscribeNavigating(); + } + }; + }, }; } diff --git a/tests/index.test.ts b/tests/index.test.ts index 6cd6bd4..6dcdf3b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -133,6 +133,26 @@ test.describe('queryParam', () => { expect(url.hash).toBe('#test-hash'); }); + test('changing multiple parameters due to subscribe updates params accordingly', async ({ + page, + }) => { + await page.goto('/paramOnNavigateBug'); + const btn = page.getByTestId('update-params'); + await btn.click(); + + // expect(url.searchParams.get('param1')).toBe(''); + + await page.waitForURL( + (url) => { + return ( + url.searchParams.get('param1') === 'updated param1' && + url.searchParams.get('param2') === 'updated param2' + ); + }, + { timeout: 1000 }, + ); + }); + test("changing two parameters in the same function doesn't negate", async ({ page, }) => {