Skip to content

fix: components mounted via mount() during onMount() that not properly update when using signals provided as input of the mount() function. #16066

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-oranges-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

Fix components mounted via mount() during onMount() that not properly update when using signals provided as input of the mount() function.
24 changes: 12 additions & 12 deletions packages/svelte/src/internal/client/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
is_array,
object_prototype
} from '../shared/utils.js';
import { state as source, set } from './reactivity/sources.js';
import { source, state, set } from './reactivity/sources.js';
import { STATE_SYMBOL } from '#client/constants';
import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js';
Expand All @@ -35,7 +35,7 @@ export function proxy(value) {
/** @type {Map<any, Source<any>>} */
var sources = new Map();
var is_proxied_array = is_array(value);
var version = source(0);
var version = state(0);

var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null;
var reaction = active_reaction;
Expand All @@ -56,9 +56,9 @@ export function proxy(value) {
};

if (is_proxied_array) {
// We need to create the length source eagerly to ensure that
// We need to create the length source (state) eagerly to ensure that
// mutations to the array are properly synced with our proxy
sources.set('length', source(/** @type {any[]} */ (value).length, stack));
sources.set('length', state(/** @type {any[]} */ (value).length, stack));
}

return new Proxy(/** @type {any} */ (value), {
Expand All @@ -79,7 +79,7 @@ export function proxy(value) {
var s = sources.get(prop);

if (s === undefined) {
s = with_parent(() => source(descriptor.value, stack));
s = with_parent(() => state(descriptor.value, stack));
sources.set(prop, s);
} else {
set(
Expand All @@ -98,7 +98,7 @@ export function proxy(value) {
if (prop in target) {
sources.set(
prop,
with_parent(() => source(UNINITIALIZED, stack))
with_parent(() => state(UNINITIALIZED, stack))
);
update_version(version);
}
Expand Down Expand Up @@ -178,7 +178,7 @@ export function proxy(value) {
(active_effect !== null && (!has || get_descriptor(target, prop)?.writable))
) {
if (s === undefined) {
s = with_parent(() => source(has ? proxy(target[prop]) : UNINITIALIZED, stack));
s = with_parent(() => state(has ? proxy(target[prop]) : UNINITIALIZED, stack));
sources.set(prop, s);
}

Expand All @@ -202,22 +202,22 @@ export function proxy(value) {
if (other_s !== undefined) {
set(other_s, UNINITIALIZED);
} else if (i in target) {
// If the item exists in the original, we need to create a uninitialized source,
// else a later read of the property would result in a source being created with
// If the item exists in the original, we need to create a uninitialized source (state),
// else a later read of the property would result in a source (state) being created with
// the value of the original item at that index.
other_s = with_parent(() => source(UNINITIALIZED, stack));
other_s = with_parent(() => state(UNINITIALIZED, stack));
sources.set(i + '', other_s);
}
}
}

// If we haven't yet created a source for this property, we need to ensure
// If we haven't yet created a source (state) for this property, we need to ensure
// we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property.
if (s === undefined) {
if (!has || get_descriptor(target, prop)?.writable) {
s = with_parent(() => source(undefined, stack));
s = with_parent(() => state(undefined, stack));
set(
s,
with_parent(() => proxy(value))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
test({ assert, target, logs }) {
const button = target.querySelector('button');

assert.htmlEqual(
target.innerHTML,
`
<button>toggle</button>
<div>
<p>First if block:</p>
<span class="first">First: true</span>
<p>Second if block:</p>
<span class="second">Second: true</span>
</div>
`
);

flushSync(() => button?.click());

assert.htmlEqual(
target.innerHTML,
`
<button>toggle</button>
<div>
<p>First if block:</p>
<p>Second if block:</p>
</div>
`
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script>
import { onMount } from 'svelte';
import { mountComponentWithContext } from './module.svelte.js';

let mountTarget;

let getShowText;
let setShowText;

onMount(() => {
const r = mountComponentWithContext(mountTarget);
getShowText = r.getShowText;
setShowText = r.setShowText;
});

function toggleState() {
setShowText(!getShowText());
}
</script>

<button onclick={() => toggleState()}>toggle</button>
<div bind:this={mountTarget}></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { mount } from 'svelte';
import Nested from './nested.svelte';

export function mountComponentWithContext(target) {
const stateObject = $state({ showText: true });

mount(Nested, {
target,
props: {},
context: new Map([['stateContext', stateObject]])
});

return {
getShowText: () => stateObject.showText,
setShowText: (newShowText) => {
stateObject.showText = newShowText;
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
import { getContext } from 'svelte';

const stateObjectFromContext = getContext('stateContext');
</script>

<p>First if block:</p>
{#if stateObjectFromContext.showText === true}
<span class="first">First: {stateObjectFromContext.showText}</span>
{/if}

<p>Second if block:</p>
{#if stateObjectFromContext.showText === true}
<span class="second">Second: {stateObjectFromContext.showText}</span>
{/if}