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.
pull/13842/head
Dominic Gannaway 11 months ago committed by GitHub
parent 96e2d5a395
commit d621f59642
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow usage of getContext() within $derived runes

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

@ -17,7 +17,8 @@ import {
skip_reaction,
update_reaction,
increment_version,
set_active_effect
set_active_effect,
component_context
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
@ -44,6 +45,7 @@ export function derived(fn) {
/** @type {Derived<V>} */
const signal = {
children: null,
ctx: component_context,
deps: null,
equals,
f: flags,
@ -169,5 +171,5 @@ export function destroy_derived(signal) {
set_signal_status(signal, DESTROYED);
// TODO we need to ensure we remove the derived from any parent derives
signal.v = signal.children = signal.deps = signal.reactions = null;
signal.v = signal.children = signal.deps = signal.ctx = signal.reactions = null;
}

@ -17,6 +17,8 @@ export interface Value<V = unknown> extends Signal {
}
export interface Reaction extends Signal {
/** The associated component context */
ctx: null | ComponentContext;
/** The reaction function */
fn: null | Function;
/** Signals that this signal reads from */
@ -40,8 +42,6 @@ export interface Effect extends Reaction {
*/
nodes_start: null | TemplateNode;
nodes_end: null | TemplateNode;
/** The associated component context */
ctx: null | ComponentContext;
/** Reactions created inside this signal */
deriveds: null | Derived[];
/** The effect function */

@ -302,6 +302,7 @@ export function update_reaction(reaction) {
var previous_reaction = active_reaction;
var previous_skip_reaction = skip_reaction;
var prev_derived_sources = derived_sources;
var previous_component_context = component_context;
var flags = reaction.f;
new_deps = /** @type {null | Value[]} */ (null);
@ -310,6 +311,7 @@ export function update_reaction(reaction) {
active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
derived_sources = null;
component_context = reaction.ctx;
try {
var result = /** @type {Function} */ (0, reaction.fn)();
@ -347,6 +349,7 @@ export function update_reaction(reaction) {
active_reaction = previous_reaction;
skip_reaction = previous_skip_reaction;
derived_sources = prev_derived_sources;
component_context = previous_component_context;
}
}
@ -422,7 +425,6 @@ export function update_effect(effect) {
var previous_component_context = component_context;
active_effect = effect;
component_context = effect.ctx;
if (DEV) {
var previous_component_fn = dev_current_component_function;
@ -449,7 +451,6 @@ export function update_effect(effect) {
handle_error(/** @type {Error} */ (error), effect, previous_component_context);
} finally {
active_effect = previous_effect;
component_context = previous_component_context;
if (DEV) {
dev_current_component_function = previous_component_fn;

@ -0,0 +1,13 @@
<script>
import { getContext } from 'svelte'
let count = $state(0);
let total = $derived(multiply(count));
function multiply(num) {
const context = getContext("key");
return num * context;
}
</script>
<button onclick={() => count++}>{total}</button>

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>0</button>`,
test({ assert, target }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(target.innerHTML, `<button>10</button>`);
}
});

@ -0,0 +1,8 @@
<script>
import { setContext } from 'svelte'
import Child from './Child.svelte'
setContext("key", 10)
</script>
<Child />

@ -8,8 +8,24 @@ export default test({
},
test({ assert, target, warnings }) {
/** @type {any} */
let error;
const handler = (/** @type {any}} */ e) => {
error = e.error;
e.stopImmediatePropagation();
};
window.addEventListener('error', handler, true);
target.querySelector('button')?.click();
assert.throws(() => {
throw error;
}, /state_unsafe_mutation/);
window.removeEventListener('error', handler, true);
assert.deepEqual(warnings, [
'`click` handler at Button.svelte:5:9 should be a function. Did you mean to add a leading `() =>`?'
]);

@ -8,8 +8,24 @@ export default test({
},
test({ assert, target, warnings }) {
/** @type {any} */
let error;
const handler = (/** @type {any} */ e) => {
error = e.error;
e.stopImmediatePropagation();
};
window.addEventListener('error', handler, true);
target.querySelector('button')?.click();
assert.throws(() => {
throw error;
}, /state_unsafe_mutation/);
window.removeEventListener('error', handler, true);
assert.deepEqual(warnings, [
'`click` handler at main.svelte:9:17 should be a function. Did you mean to remove the trailing `()`?'
]);

Loading…
Cancel
Save