fix: reset effects inside skipped branches (#17581)

* fix: prevent reactivity loss during fork

fixes #17197, fixes #17304, fixes #17301, fixes #17309

* add samples

* add changeset

* fix var casing in tests

* don't remove reactions during fork

* add sample for derived dep tracking in fork

* fix sample type check error

* set derived.v on first eval in fork

* add sample for derived.v remaining UNINITIALIZED

* lost current_batch import in runtime.js

* Delete how

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

* delete test in favour of #17577

* extract runtime.js changes into separate PR

* alternative approach

* revert

* clear skipped branches when deferring

* fix

* fix

* changeset

* rename test

* update test

* unused test

---------

Co-authored-by: David Roizenman <david@hmnd.io>
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
pull/17564/head
Rich Harris 3 months ago committed by GitHub
parent 3608b3c869
commit 1c131f11ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reset effects inside skipped branches

@ -171,6 +171,10 @@ export class Batch {
if (this.is_deferred()) {
this.#defer_effects(render_effects);
this.#defer_effects(effects);
for (const e of this.skipped_effects) {
reset_branch(e);
}
} else {
// append/remove branches
for (const fn of this.#commit_callbacks) fn();
@ -881,6 +885,26 @@ export function eager(fn) {
return value;
}
/**
* Mark all the effects inside a skipped branch CLEAN, so that
* they can be correctly rescheduled later
* @param {Effect} effect
*/
function reset_branch(effect) {
// clean branch = nothing dirty inside, no need to traverse further
if ((effect.f & BRANCH_EFFECT) !== 0 && (effect.f & CLEAN) !== 0) {
return;
}
set_signal_status(effect, CLEAN);
var e = effect.first;
while (e !== null) {
reset_branch(e);
e = e.next;
}
}
/**
* Creates a 'fork', in which state changes are evaluated but not applied to the DOM.
* This is useful for speculatively loading data (for example) when you suspect that

@ -0,0 +1,21 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
skip_no_async: true,
async test({ assert, target }) {
const [fork_btn, counter_btn] = target.querySelectorAll('button');
flushSync(() => {
fork_btn.click();
});
assert.equal(counter_btn.textContent, '0');
flushSync(() => {
counter_btn.click();
});
assert.equal(counter_btn.textContent, '1');
}
});

@ -0,0 +1,22 @@
<script>
let show = $state(false);
let show_async = $state(false);
let count = $state(0);
</script>
<button onclick={() => {
show = true;
show_async = true;
}}>show</button>
{#if show}
hi
{:else}
{#if show || !show}
<button onclick={() => count++}>{count}</button>
{/if}
{/if}
{#if show_async}
{await new Promise(() => {})}
{/if}
Loading…
Cancel
Save