Skip to content

Commit a146440

Browse files
committed
Merge remote-tracking branch 'upstream/main' into allow-state-return
2 parents 0d526ce + 3080c13 commit a146440

File tree

77 files changed

+810
-530
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+810
-530
lines changed

.changeset/nice-pianos-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: prevent state runes from being called with spread

benchmarking/compare/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from 'node:fs';
22
import path from 'node:path';
33
import { execSync, fork } from 'node:child_process';
44
import { fileURLToPath } from 'node:url';
5-
import { benchmarks } from '../benchmarks.js';
65

76
// if (execSync('git status --porcelain').toString().trim()) {
87
// console.error('Working directory is not clean');

benchmarking/compare/runner.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { benchmarks } from '../benchmarks.js';
1+
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
22

33
const results = [];
4-
for (const benchmark of benchmarks) {
4+
for (const benchmark of reactivity_benchmarks) {
55
const result = await benchmark();
66
console.error(result.benchmark);
77
results.push(result);

documentation/docs/02-runes/03-$derived.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,48 @@ Anything read synchronously inside the `$derived` expression (or `$derived.by` f
5252

5353
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
5454

55+
## Overriding derived values
56+
57+
Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them (unless they are declared with `const`). This can be useful for things like _optimistic UI_, where a value is derived from the 'source of truth' (such as data from your server) but you'd like to show immediate feedback to the user:
58+
59+
```svelte
60+
<script>
61+
let { post, like } = $props();
62+
63+
let likes = $derived(post.likes);
64+
65+
async function onclick() {
66+
// increment the `likes` count immediately...
67+
likes += 1;
68+
69+
// and tell the server, which will eventually update `post`
70+
try {
71+
await like();
72+
} catch {
73+
// failed! roll back the change
74+
likes -= 1;
75+
}
76+
}
77+
</script>
78+
79+
<button {onclick}>🧡 {likes}</button>
80+
```
81+
82+
> [!NOTE] Prior to Svelte 5.25, deriveds were read-only.
83+
84+
## Deriveds and reactivity
85+
86+
Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)...
87+
88+
```svelte
89+
let items = $state([...]);
90+
91+
let index = $state(0);
92+
let selected = $derived(items[index]);
93+
```
94+
95+
...you can change (or `bind:` to) properties of `selected` and it will affect the underlying `items` array. If `items` was _not_ deeply reactive, mutating `selected` would have no effect.
96+
5597
## Update propagation
5698

5799
Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').

documentation/docs/02-runes/04-$effect.md

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ Teardown functions also run when the effect is destroyed, which happens when its
7474

7575
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a re-run.
7676

77+
If `$state` and `$derived` are used directly inside the `$effect` (for example, during creation of a [reactive class](https://svelte.dev/docs/svelte/$state#Classes)), those values will _not_ be treated as dependencies.
78+
7779
Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
7880

7981
```ts
@@ -252,6 +254,8 @@ In general, `$effect` is best considered something of an escape hatch — useful
252254

253255
> [!NOTE] For things that are more complicated than a simple expression like `count * 2`, you can also use `$derived.by`.
254256
257+
If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
258+
255259
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
256260

257261
```svelte
@@ -280,62 +284,34 @@ You might be tempted to do something convoluted with effects to link one value t
280284
</label>
281285
```
282286

283-
Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)):
287+
Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)):
284288

285289
```svelte
286290
<script>
287291
let total = 100;
288292
let spent = $state(0);
289293
let left = $state(total);
290294
291-
function updateSpent(e) {
292-
spent = +e.target.value;
295+
function updateSpent(value) {
296+
spent = value;
293297
left = total - spent;
294298
}
295299
296-
function updateLeft(e) {
297-
left = +e.target.value;
300+
function updateLeft(value) {
301+
left = value;
298302
spent = total - left;
299303
}
300304
</script>
301305
302306
<label>
303-
<input type="range" value={spent} oninput={updateSpent} max={total} />
307+
<input type="range" bind:value={() => spent, updateSpent} max={total} />
304308
{spent}/{total} spent
305309
</label>
306310
307311
<label>
308-
<input type="range" value={left} oninput={updateLeft} max={total} />
312+
<input type="range" bind:value={() => left, updateLeft} max={total} />
309313
{left}/{total} left
310314
</label>
311315
```
312316

313-
If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)):
314-
315-
```svelte
316-
<script>
317-
let total = 100;
318-
let spent = $state(0);
319-
320-
let left = {
321-
get value() {
322-
return total - spent;
323-
},
324-
set value(v) {
325-
spent = total - v;
326-
}
327-
};
328-
</script>
329-
330-
<label>
331-
<input type="range" bind:value={spent} max={total} />
332-
{spent}/{total} spent
333-
</label>
334-
335-
<label>
336-
<input type="range" bind:value={left.value} max={total} />
337-
{left.value}/{total} left
338-
</label>
339-
```
340-
341317
If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack).

documentation/docs/06-runtime/02-context.md

Lines changed: 86 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,129 +2,137 @@
22
title: Context
33
---
44

5-
<!-- - get/set/hasContext
6-
- how to use, best practises (like encapsulating them) -->
5+
Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`...
76

8-
Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow.
9-
10-
The easiest way to do that is to create global state and just import that.
7+
```svelte
8+
<!--- file: Parent.svelte --->
9+
<script>
10+
import { setContext } from 'svelte';
1111
12-
```ts
13-
/// file: state.svelte.js
14-
export const myGlobalState = $state({
15-
user: {
16-
/* ... */
17-
}
18-
/* ... */
19-
});
12+
setContext('my-context', 'hello from Parent.svelte');
13+
</script>
2014
```
2115

16+
...and the child retrieves it with `getContext`:
17+
2218
```svelte
23-
<!--- file: App.svelte --->
19+
<!--- file: Child.svelte --->
2420
<script>
25-
import { myGlobalState } from './state.svelte.js';
26-
// ...
21+
import { getContext } from 'svelte';
22+
23+
const message = getContext('my-context');
2724
</script>
25+
26+
<h1>{message}, inside Child.svelte</h1>
2827
```
2928

30-
This has a few drawbacks though:
29+
This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)):
30+
31+
```svelte
32+
<Parent>
33+
<Child />
34+
</Parent>
35+
```
3136

32-
- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs
33-
- it may give the false impression that certain state is global when in reality it should only be used in a certain part of your app
37+
The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value.
3438

35-
To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems.
39+
In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions.
3640

37-
## Setting and getting context
41+
## Using context with state
3842

39-
To associate an arbitrary object with the current component, use `setContext`.
43+
You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))...
4044

4145
```svelte
4246
<script>
4347
import { setContext } from 'svelte';
48+
import Child from './Child.svelte';
4449
45-
setContext('key', value);
50+
let counter = $state({
51+
count: 0
52+
});
53+
54+
setContext('counter', counter);
4655
</script>
56+
57+
<button onclick={() => counter.count += 1}>
58+
increment
59+
</button>
60+
61+
<Child />
62+
<Child />
63+
<Child />
4764
```
4865

49-
The context is then available to children of the component (including slotted content) with `getContext`.
66+
...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this...
5067

5168
```svelte
52-
<script>
53-
import { getContext } from 'svelte';
54-
55-
const value = getContext('key');
56-
</script>
69+
<button onclick={() => counter = { count: 0 }}>
70+
reset
71+
</button>
5772
```
5873

59-
`setContext` and `getContext` solve the above problems:
74+
...you must do this:
6075

61-
- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state
62-
- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app
76+
```svelte
77+
<button onclick={() => +++counter.count = 0+++}>
78+
reset
79+
</button>
80+
```
6381

64-
> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
82+
Svelte will warn you if you get it wrong.
6583

66-
Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive.
84+
## Type-safe context
6785

68-
```svelte
69-
<!--- file: Parent.svelte --->
70-
<script>
71-
import { setContext } from 'svelte';
86+
A useful pattern is to wrap the calls to `setContext` and `getContext` inside helper functions that let you preserve type safety:
7287

73-
let value = $state({ count: 0 });
74-
setContext('counter', value);
75-
</script>
88+
```js
89+
/// file: context.js
90+
// @filename: ambient.d.ts
91+
interface User {}
7692

77-
<button onclick={() => value.count++}>increment</button>
78-
```
93+
// @filename: index.js
94+
// ---cut---
95+
import { getContext, setContext } from 'svelte';
7996

80-
```svelte
81-
<!--- file: Child.svelte --->
82-
<script>
83-
import { getContext } from 'svelte';
97+
let key = {};
8498

85-
const value = getContext('counter');
86-
</script>
99+
/** @param {User} user */
100+
export function setUserContext(user) {
101+
setContext(key, user);
102+
}
87103

88-
<p>Count is {value.count}</p>
104+
export function getUserContext() {
105+
return /** @type {User} */ (getContext(key));
106+
}
89107
```
90108

91-
To check whether a given `key` has been set in the context of a parent component, use `hasContext`.
109+
## Replacing global state
92110

93-
```svelte
94-
<script>
95-
import { hasContext } from 'svelte';
111+
When you have state shared by many different components, you might be tempted to put it in its own module and just import it wherever it's needed:
96112

97-
if (hasContext('key')) {
98-
// do something
113+
```js
114+
/// file: state.svelte.js
115+
export const myGlobalState = $state({
116+
user: {
117+
// ...
99118
}
100-
</script>
119+
// ...
120+
});
101121
```
102122

103-
You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it.
123+
In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)...
104124

105125
```svelte
126+
<!--- file: App.svelte ---->
106127
<script>
107-
import { getAllContexts } from 'svelte';
128+
import { myGlobalState } from 'svelte';
108129
109-
const contexts = getAllContexts();
130+
let { data } = $props();
131+
132+
if (data.user) {
133+
myGlobalState.user = data.user;
134+
}
110135
</script>
111136
```
112137

113-
## Encapsulating context interactions
114-
115-
The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them.
116-
117-
```ts
118-
// @errors: 2304
119-
import { getContext, setContext } from 'svelte';
120-
121-
let userKey = Symbol('user');
122-
123-
export function setUserContext(user: User) {
124-
setContext(userKey, user);
125-
}
126-
127-
export function getUserContext(): User {
128-
return getContext(userKey) as User;
129-
}
130-
```
138+
...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests.

0 commit comments

Comments
 (0)