Skip to content

Commit 8293144

Browse files
dummdidummRich-Harristeemingc
authored
breaking: deprecate invalidate/invalidateAll (#16289)
- Add option to `invalidate` to not reset `page.state` (we don't want to add `refresh` for this as we may want to introduce it in relation to queries and not for load functions) - Deprecated `invalidateAll` in favor of `refreshAll` (also on `goto`) - Removed the `includeLoadFunctions` option from `refreshAll` - it now always reruns `load` functions Closes #11783 Closes #13139 --------- Co-authored-by: Rich Harris <richard.a.harris@gmail.com> Co-authored-by: Tee Ming <chewteeming01@gmail.com> Co-authored-by: Rich Harris <rich.harris@vercel.com>
1 parent d561656 commit 8293144

23 files changed

Lines changed: 222 additions & 141 deletions

File tree

.changeset/refresh-page-state.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': major
3+
---
4+
5+
breaking: add `refreshAll` and deprecate `invalidateAll`

documentation/docs/20-core-concepts/20-load.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,9 @@ export async function load({ untrack, url }) {
638638

639639
### Manual invalidation
640640

641-
You can also rerun `load` functions that apply to the current page using [`invalidate(url)`]($app-navigation#invalidate), which reruns all `load` functions that depend on `url`, and [`invalidateAll()`]($app-navigation#invalidateAll), which reruns every `load` function. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client.
641+
You can also rerun `load` functions that apply to the current page using [`invalidate(url)`]($app-navigation#invalidate), which reruns all `load` functions that depend on `url`, and [`refreshAll()`]($app-navigation#refreshAll), which reruns every `load` function and all active queries. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client.
642+
643+
> [!NOTE] `refreshAll` does _not_ reset `page.state`, unlike its deprecated predecessor `invalidateAll`.
642644
643645
A `load` function depends on `url` if it calls `fetch(url)` or `depends(url)`. Note that `url` can be a custom identifier that starts with `[a-z]:`:
644646

@@ -661,7 +663,7 @@ export async function load({ fetch, depends }) {
661663
```svelte
662664
<!--- file: src/routes/random-number/+page.svelte --->
663665
<script>
664-
import { invalidate, invalidateAll } from '$app/navigation';
666+
import { invalidate, refreshAll } from '$app/navigation';
665667
666668
/** @type {import('./$types').PageProps} */
667669
let { data } = $props();
@@ -671,7 +673,7 @@ export async function load({ fetch, depends }) {
671673
invalidate('app:random');
672674
invalidate('https://api.example.com/random-number');
673675
invalidate(url => url.href.includes('random-number'));
674-
invalidateAll();
676+
refreshAll();
675677
}
676678
</script>
677679
@@ -689,7 +691,7 @@ To summarize, a `load` function will rerun in the following situations:
689691
- It calls `await parent()` and a parent `load` function reran
690692
- A child `load` function calls `await parent()` and is rerunning, and the parent is a server load function
691693
- It declared a dependency on a specific URL via [`fetch`](#Making-fetch-requests) (universal load only) or [`depends`](@sveltejs-kit#LoadEvent), and that URL was marked invalid with [`invalidate(url)`]($app-navigation#invalidate)
692-
- All active `load` functions were forcibly rerun with [`invalidateAll()`]($app-navigation#invalidateAll)
694+
- All active `load` functions were forcibly rerun with [`refreshAll()`]($app-navigation#refreshAll)
693695

694696
`params` and `url` can change in response to a `<a href="..">` link click, a [`<form>` interaction](form-actions#GET-vs-POST), a [`goto`]($app-navigation#goto) invocation, or a [`redirect`](@sveltejs-kit#redirect).
695697

documentation/docs/20-core-concepts/30-form-actions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
433433
```svelte
434434
<!--- file: src/routes/login/+page.svelte --->
435435
<script>
436-
import { invalidateAll, goto } from '$app/navigation';
436+
import { refreshAll, goto } from '$app/navigation';
437437
import { applyAction, deserialize } from '$app/forms';
438438

439439
/** @type {import('./$types').PageProps} */
@@ -453,8 +453,8 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
453453
const result = deserialize(await response.text());
454454

455455
if (result.type === 'success') {
456-
// rerun all `load` functions, following the successful update
457-
await invalidateAll();
456+
// rerun all `load` functions and queries, following the successful update
457+
await refreshAll();
458458
}
459459

460460
applyAction(result);

packages/kit/src/runtime/app/forms.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as devalue from 'devalue';
22
import { BROWSER, DEV } from 'esm-env';
33
import { noop } from '../../utils/functions.js';
4-
import { invalidateAll } from './navigation.js';
4+
import { refreshAll } from './navigation.js';
55
import { app as client_app, applyAction, handle_error } from '../client/client.js';
66
import { app as server_app } from '../server/app.js';
77

@@ -106,7 +106,7 @@ export function enhance(form_element, submit = noop) {
106106
HTMLFormElement.prototype.reset.call(form_element);
107107
}
108108
if (shouldInvalidateAll) {
109-
await invalidateAll();
109+
await refreshAll();
110110
}
111111
}
112112

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

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ export async function start(_app, _target, hydrate) {
414414
_start_router();
415415
}
416416

417-
async function _invalidate(include_load_functions = true, reset_page_state = true) {
417+
async function _invalidate(reset_page_state = true) {
418418
// Accept all invalidations as they come, don't swallow any while another invalidation
419419
// is running because subsequent invalidations may make earlier ones outdated,
420420
// but batch multiple synchronous invalidations.
@@ -453,39 +453,35 @@ async function _invalidate(include_load_functions = true, reset_page_state = tru
453453
}
454454
}
455455

456-
if (include_load_functions) {
457-
const prev_state = page.state;
458-
const navigation_result = intent && (await load_route(intent));
459-
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
460-
return;
461-
}
456+
const prev_state = page.state;
457+
const navigation_result = intent && (await load_route(intent));
458+
if (!navigation_result || token !== invalidation_token || nav_token !== navigation_token) {
459+
return;
460+
}
462461

463-
if (navigation_result.type === 'redirect') {
464-
return _goto(
465-
new URL(navigation_result.location, current.url).href,
466-
{ replaceState: true },
467-
1,
468-
token
469-
);
470-
}
462+
if (navigation_result.type === 'redirect') {
463+
return _goto(
464+
new URL(navigation_result.location, current.url).href,
465+
{ replaceState: true },
466+
1,
467+
token
468+
);
469+
}
471470

472-
// A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
473-
// hence it likely contains outdated data now, so we ignore it.
474-
if (navigating && !is_navigating) {
475-
return;
476-
}
471+
// A navigation started before the invalidation and ended before it finished. The invalidation did not redirect,
472+
// hence it likely contains outdated data now, so we ignore it.
473+
if (navigating && !is_navigating) {
474+
return;
475+
}
477476

478-
// This is a bit hacky but allows us not having to pass that boolean around, making things harder to reason about
479-
if (!reset_page_state) {
480-
navigation_result.props.page.state = prev_state;
481-
}
482-
update(navigation_result.props.page);
483-
current = { ...navigation_result.state, nav: current.nav };
484-
reset_invalidation();
485-
root.$set(navigation_result.props);
486-
} else {
487-
reset_invalidation();
477+
// Preserve `page.state` when invalidating without resetting it (e.g. `refresh`/`refreshAll`)
478+
if (!reset_page_state) {
479+
navigation_result.props.page.state = prev_state;
488480
}
481+
update(navigation_result.props.page);
482+
current = { ...navigation_result.state, nav: current.nav };
483+
reset_invalidation();
484+
root.$set(navigation_result.props);
489485

490486
// only wait for promises that are connected to queries that still exist
491487
/** @type {Promise<any>[]} */
@@ -538,7 +534,7 @@ function persist_state() {
538534

539535
/**
540536
* @param {string | URL} url
541-
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; invalidateAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
537+
* @param {{ replaceState?: boolean; noScroll?: boolean; keepFocus?: boolean; refreshAll?: boolean; invalidate?: Array<string | URL | ((url: URL) => boolean)>; state?: Record<string, any> }} options
542538
* @param {number} redirect_count
543539
* @param {{}} [nav_token]
544540
* @param {NavigationIntent | undefined} [intent] navigation intent, when already known by the caller (avoids recomputing it)
@@ -550,9 +546,9 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
550546
/** @type {Set<string>} */
551547
let live_query_keys;
552548

553-
// Clear preload cache when invalidateAll is true to ensure fresh data
549+
// Clear preload cache when refreshAll is true to ensure fresh data
554550
// after form submissions or explicit invalidations
555-
if (options.invalidateAll) {
551+
if (options.refreshAll) {
556552
discard_load_cache();
557553
}
558554

@@ -567,7 +563,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
567563
nav_token,
568564
intent,
569565
accept: () => {
570-
if (options.invalidateAll) {
566+
if (options.refreshAll) {
571567
force_invalidation = true;
572568
query_keys = new Set();
573569
for (const [id, entries] of query_map) {
@@ -593,7 +589,7 @@ export async function _goto(url, options, redirect_count, nav_token, intent) {
593589
}
594590
});
595591

596-
if (options.invalidateAll) {
592+
if (options.refreshAll) {
597593
// TODO the ticks shouldn't be necessary, something inside Svelte itself is buggy
598594
// when a query in a layout that still exists after page change is refreshed earlier than this
599595
void svelte
@@ -2322,6 +2318,8 @@ export function disableScrollHandling() {
23222318
}
23232319
}
23242320

2321+
let warned_on_invalidate_all = false;
2322+
23252323
/**
23262324
* Allows you to navigate programmatically to a given route, with options such as keeping the current element focused.
23272325
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified `url`.
@@ -2335,8 +2333,9 @@ export function disableScrollHandling() {
23352333
* @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState`
23362334
* @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation
23372335
* @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body
2338-
* @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
2336+
* @param {boolean} [opts.refreshAll] If `true`, all `load` functions and queries of the page will be rerun. See https://svelte.dev/docs/kit/load#rerunning-load-functions for more info on invalidation.
23392337
* @param {Array<string | URL | ((url: URL) => boolean)>} [opts.invalidate] Causes any load functions to re-run if they depend on one of the urls
2338+
* @param {boolean} [opts.invalidateAll] Deprecated in favour of opts.refreshAll.
23402339
* @param {App.PageState} [opts.state] An optional object that will be available as `page.state`
23412340
* @returns {Promise<void>}
23422341
*/
@@ -2365,6 +2364,14 @@ export async function goto(url, opts = {}) {
23652364
);
23662365
}
23672366

2367+
if (DEV && 'invalidateAll' in opts && !warned_on_invalidate_all) {
2368+
warned_on_invalidate_all = true;
2369+
console.warn(
2370+
`The \`goto(..., { invalidateAll: ${opts.invalidateAll} })\` option has been deprecated in favour of \`refreshAll\``
2371+
);
2372+
}
2373+
2374+
opts.refreshAll = opts.refreshAll ?? opts.invalidateAll;
23682375
return _goto(url, opts, 0, {}, intent);
23692376
}
23702377

@@ -2384,16 +2391,17 @@ export async function goto(url, opts = {}) {
23842391
* invalidate((url) => url.pathname === '/path');
23852392
* ```
23862393
* @param {string | URL | ((url: URL) => boolean)} resource The invalidated URL
2394+
* @param {boolean} [keepState] If `true`, the current `page.state` will be preserved. Otherwise, it will be reset to an empty object. `false` by default.
23872395
* @returns {Promise<void>}
23882396
*/
2389-
export function invalidate(resource) {
2397+
export function invalidate(resource, keepState = false) {
23902398
if (!BROWSER) {
23912399
throw new Error('Cannot call invalidate(...) on the server');
23922400
}
23932401

23942402
push_invalidated(resource);
23952403

2396-
return _invalidate();
2404+
return _invalidate(!keepState);
23972405
}
23982406

23992407
/**
@@ -2410,6 +2418,10 @@ function push_invalidated(resource) {
24102418

24112419
/**
24122420
* Causes all `load` and `query` functions belonging to the currently active page to re-run. Returns a `Promise` that resolves when the page is subsequently updated.
2421+
*
2422+
* Note that this resets `page.state` to an empty object. If you want to preserve `page.state` (for example when using [shallow routing](https://svelte.dev/docs/kit/shallow-routing)), use `refreshAll` instead.
2423+
*
2424+
* @deprecated Use [`refreshAll`](https://svelte.dev/docs/kit/$app-navigation#refreshAll) instead. Unlike `invalidateAll`, `refreshAll` does not reset `page.state`.
24132425
* @returns {Promise<void>}
24142426
*/
24152427
export function invalidateAll() {
@@ -2422,18 +2434,17 @@ export function invalidateAll() {
24222434
}
24232435

24242436
/**
2425-
* Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run (unless disabled via the option argument).
2437+
* Causes all currently active remote functions to refresh, and all `load` functions belonging to the currently active page to re-run.
24262438
* Returns a `Promise` that resolves when the page is subsequently updated.
2427-
* @param {{ includeLoadFunctions?: boolean }} [options]
24282439
* @returns {Promise<void>}
24292440
*/
2430-
export function refreshAll({ includeLoadFunctions = true } = {}) {
2441+
export function refreshAll() {
24312442
if (!BROWSER) {
24322443
throw new Error('Cannot call refreshAll() on the server');
24332444
}
24342445

24352446
force_invalidation = true;
2436-
return _invalidate(includeLoadFunctions, false);
2447+
return _invalidate(false);
24372448
}
24382449

24392450
/**
@@ -2624,7 +2635,7 @@ export async function applyAction(result) {
26242635
if (result.type === 'error') {
26252636
await set_nearest_error_page(result.error);
26262637
} else if (result.type === 'redirect') {
2627-
await _goto(result.location, { invalidateAll: true }, 0);
2638+
await _goto(result.location, { refreshAll: true }, 0);
26282639
} else {
26292640
page.form = result.data;
26302641
page.status = result.status;

packages/kit/src/runtime/client/remote-functions/form.svelte.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
query_responses,
99
_goto,
1010
set_nearest_error_page,
11-
invalidateAll,
12-
handle_error
11+
handle_error,
12+
refreshAll
1313
} from '../client.js';
1414
import { tick } from 'svelte';
1515
import { categorize_updates, remote_request } from './shared.svelte.js';
@@ -227,14 +227,14 @@ export function form(id) {
227227

228228
// if the developer took control of updates via `.updates(...)` (even with
229229
// no arguments), or the server performed explicit refreshes, don't invalidateAll
230-
const should_invalidate = refreshes === null && !response.r;
230+
const should_refresh = refreshes === null && !response.r;
231231

232232
if (response.redirect) {
233233
// Use internal version to allow redirects to external URLs
234234
void _goto(
235235
response.redirect,
236236
{
237-
invalidateAll: should_invalidate
237+
refreshAll: should_refresh
238238
},
239239
0
240240
);
@@ -244,8 +244,8 @@ export function form(id) {
244244
const succeeded = raw_issues.length === 0;
245245

246246
if (succeeded) {
247-
if (should_invalidate) {
248-
void invalidateAll();
247+
if (should_refresh) {
248+
void refreshAll();
249249
}
250250
} else {
251251
if (DEV) {

packages/kit/test/apps/async/src/routes/remote/+page.svelte

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,6 @@
172172
</button>
173173

174174
<button id="refresh-all" onclick={() => refreshAll()}>refreshAll</button>
175-
<button id="refresh-remote-only" onclick={() => refreshAll({ includeLoadFunctions: false })}>
176-
refreshAll (remote functions only)
177-
</button>
178175
<button id="resolve-deferreds" onclick={() => resolve_deferreds()}>Resolve Deferreds</button>
179176

180177
<a href="/remote/event">/remote/event</a>

packages/kit/test/apps/async/src/routes/remote/live/+page.svelte

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { invalidateAll } from '$app/navigation';
2+
import { refreshAll } from '$app/navigation';
33
import LiveView from './LiveView.svelte';
44
import {
55
increment,
@@ -67,15 +67,15 @@
6767
}
6868
}
6969
70-
let invalidate_state = $state('idle');
70+
let refresh_state = $state('idle');
7171
72-
async function run_invalidate_all() {
73-
invalidate_state = 'pending';
72+
async function run_refresh_all() {
73+
refresh_state = 'pending';
7474
try {
75-
await invalidateAll();
76-
invalidate_state = 'resolved';
75+
await refreshAll();
76+
refresh_state = 'resolved';
7777
} catch {
78-
invalidate_state = 'rejected';
78+
refresh_state = 'rejected';
7979
}
8080
}
8181
</script>
@@ -111,5 +111,5 @@
111111

112112
<button id="start-stream-log" onclick={start_stream_log}>start stream log</button>
113113
<p id="stream-log">{stream_log}</p>
114-
<button id="run-invalidate-all" onclick={run_invalidate_all}>invalidate all</button>
115-
<p id="invalidate-state">{invalidate_state}</p>
114+
<button id="run-refresh-all" onclick={run_refresh_all}>refresh all</button>
115+
<p id="refresh-state">{refresh_state}</p>

0 commit comments

Comments
 (0)