Skip to content

Commit d621f59

Browse files
authored
feat: allow usage of getContext() within $derived runes (#13830)
Closes #13493. This PR allows the usage of getContext() inside $derived runes. Previously, you could use it, but only on init and not updates – and this inconsistency was unnecessary. We can make it work just like we do in other places.
1 parent 96e2d5a commit d621f59

File tree

10 files changed

+84
-7
lines changed

10 files changed

+84
-7
lines changed

.changeset/beige-llamas-grab.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+
feat: allow usage of getContext() within $derived runes

packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ export function apply(
314314

315315
if (typeof handler === 'function') {
316316
handler.apply(element, args);
317-
} else if (has_side_effects || handler != null) {
317+
} else if (has_side_effects || handler != null || error) {
318318
const filename = component?.[FILENAME];
319319
const location = loc ? ` at ${filename}:${loc[0]}:${loc[1]}` : ` in ${filename}`;
320320

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
skip_reaction,
1818
update_reaction,
1919
increment_version,
20-
set_active_effect
20+
set_active_effect,
21+
component_context
2122
} from '../runtime.js';
2223
import { equals, safe_equals } from './equality.js';
2324
import * as e from '../errors.js';
@@ -44,6 +45,7 @@ export function derived(fn) {
4445
/** @type {Derived<V>} */
4546
const signal = {
4647
children: null,
48+
ctx: component_context,
4749
deps: null,
4850
equals,
4951
f: flags,
@@ -169,5 +171,5 @@ export function destroy_derived(signal) {
169171
set_signal_status(signal, DESTROYED);
170172

171173
// TODO we need to ensure we remove the derived from any parent derives
172-
signal.v = signal.children = signal.deps = signal.reactions = null;
174+
signal.v = signal.children = signal.deps = signal.ctx = signal.reactions = null;
173175
}

packages/svelte/src/internal/client/reactivity/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface Value<V = unknown> extends Signal {
1717
}
1818

1919
export interface Reaction extends Signal {
20+
/** The associated component context */
21+
ctx: null | ComponentContext;
2022
/** The reaction function */
2123
fn: null | Function;
2224
/** Signals that this signal reads from */
@@ -40,8 +42,6 @@ export interface Effect extends Reaction {
4042
*/
4143
nodes_start: null | TemplateNode;
4244
nodes_end: null | TemplateNode;
43-
/** The associated component context */
44-
ctx: null | ComponentContext;
4545
/** Reactions created inside this signal */
4646
deriveds: null | Derived[];
4747
/** The effect function */

packages/svelte/src/internal/client/runtime.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ export function update_reaction(reaction) {
302302
var previous_reaction = active_reaction;
303303
var previous_skip_reaction = skip_reaction;
304304
var prev_derived_sources = derived_sources;
305+
var previous_component_context = component_context;
305306
var flags = reaction.f;
306307

307308
new_deps = /** @type {null | Value[]} */ (null);
@@ -310,6 +311,7 @@ export function update_reaction(reaction) {
310311
active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
311312
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
312313
derived_sources = null;
314+
component_context = reaction.ctx;
313315

314316
try {
315317
var result = /** @type {Function} */ (0, reaction.fn)();
@@ -347,6 +349,7 @@ export function update_reaction(reaction) {
347349
active_reaction = previous_reaction;
348350
skip_reaction = previous_skip_reaction;
349351
derived_sources = prev_derived_sources;
352+
component_context = previous_component_context;
350353
}
351354
}
352355

@@ -422,7 +425,6 @@ export function update_effect(effect) {
422425
var previous_component_context = component_context;
423426

424427
active_effect = effect;
425-
component_context = effect.ctx;
426428

427429
if (DEV) {
428430
var previous_component_fn = dev_current_component_function;
@@ -449,7 +451,6 @@ export function update_effect(effect) {
449451
handle_error(/** @type {Error} */ (error), effect, previous_component_context);
450452
} finally {
451453
active_effect = previous_effect;
452-
component_context = previous_component_context;
453454

454455
if (DEV) {
455456
dev_current_component_function = previous_component_fn;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import { getContext } from 'svelte'
3+
4+
let count = $state(0);
5+
let total = $derived(multiply(count));
6+
7+
function multiply(num) {
8+
const context = getContext("key");
9+
return num * context;
10+
}
11+
</script>
12+
13+
<button onclick={() => count++}>{total}</button>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>0</button>`,
6+
7+
test({ assert, target }) {
8+
const btn = target.querySelector('button');
9+
10+
flushSync(() => {
11+
btn?.click();
12+
});
13+
14+
assert.htmlEqual(target.innerHTML, `<button>10</button>`);
15+
}
16+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { setContext } from 'svelte'
3+
import Child from './Child.svelte'
4+
setContext("key", 10)
5+
</script>
6+
7+
<Child />
8+

packages/svelte/tests/runtime-runes/samples/event-handler-component-invalid-warning/_config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ export default test({
88
},
99

1010
test({ assert, target, warnings }) {
11+
/** @type {any} */
12+
let error;
13+
14+
const handler = (/** @type {any}} */ e) => {
15+
error = e.error;
16+
e.stopImmediatePropagation();
17+
};
18+
19+
window.addEventListener('error', handler, true);
20+
1121
target.querySelector('button')?.click();
1222

23+
assert.throws(() => {
24+
throw error;
25+
}, /state_unsafe_mutation/);
26+
27+
window.removeEventListener('error', handler, true);
28+
1329
assert.deepEqual(warnings, [
1430
'`click` handler at Button.svelte:5:9 should be a function. Did you mean to add a leading `() =>`?'
1531
]);

packages/svelte/tests/runtime-runes/samples/event-handler-invalid-warning/_config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ export default test({
88
},
99

1010
test({ assert, target, warnings }) {
11+
/** @type {any} */
12+
let error;
13+
14+
const handler = (/** @type {any} */ e) => {
15+
error = e.error;
16+
e.stopImmediatePropagation();
17+
};
18+
19+
window.addEventListener('error', handler, true);
20+
1121
target.querySelector('button')?.click();
1222

23+
assert.throws(() => {
24+
throw error;
25+
}, /state_unsafe_mutation/);
26+
27+
window.removeEventListener('error', handler, true);
28+
1329
assert.deepEqual(warnings, [
1430
'`click` handler at main.svelte:9:17 should be a function. Did you mean to remove the trailing `()`?'
1531
]);

0 commit comments

Comments
 (0)