Skip to content

Commit af747f6

Browse files
committed
fix: on teardown, use the last known value for the signal before the se
1 parent 2c4d85b commit af747f6

File tree

17 files changed

+258
-26
lines changed

17 files changed

+258
-26
lines changed
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: on teardown, use the last known value for the signal before the set

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export function CallExpression(node, context) {
4242
e.bindable_invalid_location(node);
4343
}
4444

45+
// We need context in case the bound prop is stale
46+
context.state.analysis.needs_context = true;
47+
4548
break;
4649

4750
case '$host':

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
set_active_reaction,
1212
untrack
1313
} from './runtime.js';
14-
import { effect } from './reactivity/effects.js';
14+
import { effect, teardown } from './reactivity/effects.js';
1515
import { legacy_mode_flag } from '../flags/index.js';
1616

1717
/** @type {ComponentContext | null} */
@@ -112,15 +112,16 @@ export function getAllContexts() {
112112
* @returns {void}
113113
*/
114114
export function push(props, runes = false, fn) {
115-
component_context = {
115+
var ctx = (component_context = {
116116
p: component_context,
117117
c: null,
118+
d: false,
118119
e: null,
119120
m: false,
120121
s: props,
121122
x: null,
122123
l: null
123-
};
124+
});
124125

125126
if (legacy_mode_flag && !runes) {
126127
component_context.l = {
@@ -131,6 +132,10 @@ export function push(props, runes = false, fn) {
131132
};
132133
}
133134

135+
teardown(() => {
136+
/** @type {ComponentContext} */ (ctx).d = true;
137+
});
138+
134139
if (DEV) {
135140
// component function
136141
component_context.function = fn;

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Source } from './types.js' */
1+
/** @import { Derived, Source } from './types.js' */
22
import { DEV } from 'esm-env';
33
import {
44
PROPS_IS_BINDABLE,
@@ -11,23 +11,15 @@ import { get_descriptor, is_function } from '../../shared/utils.js';
1111
import { mutable_source, set, source, update } from './sources.js';
1212
import { derived, derived_safe_equal } from './deriveds.js';
1313
import {
14-
active_effect,
1514
get,
1615
captured_signals,
17-
set_active_effect,
1816
untrack,
19-
active_reaction,
20-
set_active_reaction
17+
set_is_destroying_effect,
18+
is_destroying_effect
2119
} from '../runtime.js';
2220
import { safe_equals } from './equality.js';
2321
import * as e from '../errors.js';
24-
import {
25-
BRANCH_EFFECT,
26-
LEGACY_DERIVED_PROP,
27-
LEGACY_PROPS,
28-
ROOT_EFFECT,
29-
STATE_SYMBOL
30-
} from '../constants.js';
22+
import { LEGACY_DERIVED_PROP, LEGACY_PROPS, STATE_SYMBOL } from '../constants.js';
3123
import { proxy } from '../proxy.js';
3224
import { capture_store_binding } from './store.js';
3325
import { legacy_mode_flag } from '../../flags/index.js';
@@ -249,6 +241,14 @@ export function spread_props(...props) {
249241
return new Proxy({ props }, spread_props_handler);
250242
}
251243

244+
/**
245+
* @param {Derived} current_value
246+
* @returns {boolean}
247+
*/
248+
function has_destoyed_component_ctx(current_value) {
249+
return current_value.ctx?.d ?? false;
250+
}
251+
252252
/**
253253
* This function is responsible for synchronizing a possibly bound prop with the inner component state.
254254
* It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
@@ -382,6 +382,11 @@ export function prop(props, key, flags, fallback) {
382382
return (inner_current_value.v = parent_value);
383383
});
384384

385+
// Ensure we eagerly capture the initial value if it's bindable
386+
if (bindable) {
387+
get(current_value);
388+
}
389+
385390
if (!immutable) current_value.equals = safe_equals;
386391

387392
return function (/** @type {any} */ value, /** @type {boolean} */ mutation) {
@@ -408,11 +413,21 @@ export function prop(props, key, flags, fallback) {
408413
if (fallback_used && fallback_value !== undefined) {
409414
fallback_value = new_value;
410415
}
416+
417+
if (has_destoyed_component_ctx(current_value)) {
418+
return value;
419+
}
420+
411421
untrack(() => get(current_value)); // force a synchronisation immediately
412422
}
413423

414424
return value;
415425
}
426+
427+
if (has_destoyed_component_ctx(current_value)) {
428+
return current_value.v;
429+
}
430+
416431
return get(current_value);
417432
};
418433
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
derived_sources,
1515
set_derived_sources,
1616
check_dirtiness,
17-
untracking
17+
untracking,
18+
old_values,
19+
is_destroying_effect
1820
} from '../runtime.js';
1921
import { equals, safe_equals } from './equality.js';
2022
import {
@@ -32,6 +34,7 @@ import * as e from '../errors.js';
3234
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
3335
import { get_stack } from '../dev/tracing.js';
3436
import { component_context, is_runes } from '../context.js';
37+
import { queue_micro_task } from '../dom/task.js';
3538

3639
export let inspect_effects = new Set();
3740

@@ -168,6 +171,13 @@ export function set(source, value) {
168171
export function internal_set(source, value) {
169172
if (!source.equals(value)) {
170173
var old_value = source.v;
174+
175+
if (is_destroying_effect) {
176+
old_values.set(source, value);
177+
} else {
178+
old_values.set(source, old_value);
179+
}
180+
171181
source.v = value;
172182
source.wv = increment_write_version();
173183

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
DISCONNECTED,
2525
BOUNDARY_EFFECT
2626
} from './constants.js';
27-
import { flush_tasks } from './dom/task.js';
27+
import { flush_tasks, queue_micro_task } from './dom/task.js';
2828
import { internal_set } from './reactivity/sources.js';
2929
import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js';
3030
import * as e from './errors.js';
@@ -40,6 +40,8 @@ import {
4040
} from './context.js';
4141
import { is_firefox } from './dom/operations.js';
4242

43+
export const old_values = new Map();
44+
4345
// Used for DEV time error handling
4446
/** @param {WeakSet<Error>} value */
4547
const handled_errors = new WeakSet();
@@ -673,6 +675,7 @@ function flush_queued_root_effects() {
673675
if (DEV) {
674676
dev_effect_stack = [];
675677
}
678+
old_values.clear();
676679
}
677680
}
678681

@@ -923,6 +926,10 @@ export function get(signal) {
923926
}
924927
}
925928

929+
if (is_destroying_effect && old_values.has(signal)) {
930+
return old_values.get(signal);
931+
}
932+
926933
return signal.v;
927934
}
928935

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export type ComponentContext = {
1414
p: null | ComponentContext;
1515
/** context */
1616
c: null | Map<unknown, unknown>;
17+
/** destroyed */
18+
d: boolean;
1719
/** deferred effects */
1820
e: null | Array<{
1921
fn: () => void | (() => void);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import { onDestroy } from 'svelte';
3+
4+
export let my_prop;
5+
6+
onDestroy(() => {
7+
console.log(my_prop.foo);
8+
});
9+
</script>
10+
11+
{my_prop.foo}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
async test({ assert, target, logs }) {
6+
const [btn1] = target.querySelectorAll('button');
7+
8+
flushSync(() => {
9+
btn1.click();
10+
});
11+
12+
assert.deepEqual(logs, ['bar']);
13+
}
14+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
import Component from './Component.svelte';
3+
4+
let value = { foo: 'bar' };
5+
</script>
6+
7+
<button
8+
onclick={() => {
9+
value = undefined;
10+
}}>Reset value</button
11+
>
12+
13+
{#if value !== undefined}
14+
<Component my_prop={value} />
15+
{/if}

0 commit comments

Comments
 (0)