fix: ensure derived effects reconnect after disconnection

Adds HAS_EFFECTS flag to track when deriveds contain side effects and
re-runs computation when effects are missing after reconnection.
pull/16595/head
Mattias Granlund 6 days ago
parent 942eaf027b
commit 9b5886d616

@ -0,0 +1,5 @@
---
'svelte': patch
---
Recreate derived's effects when it reconnects

@ -25,6 +25,7 @@ export const REACTION_IS_UPDATING = 1 << 21;
export const ASYNC = 1 << 22;
export const ERROR_VALUE = 1 << 23;
export const HAS_EFFECTS = 1 << 24;
export const STATE_SYMBOL = Symbol('$state');
export const LEGACY_PROPS = Symbol('legacy props');

@ -33,7 +33,8 @@ import {
EFFECT_PRESERVED,
STALE_REACTION,
USER_EFFECT,
ASYNC
ASYNC,
HAS_EFFECTS
} from '#client/constants';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
@ -167,6 +168,7 @@ function create_effect(type, fn, sync, push = true) {
var derived = /** @type {Derived} */ (active_reaction);
(derived.effects ??= []).push(e);
}
derived.f |= HAS_EFFECTS;
}
}

@ -20,7 +20,8 @@ import {
DISCONNECTED,
REACTION_IS_UPDATING,
STALE_REACTION,
ERROR_VALUE
ERROR_VALUE,
HAS_EFFECTS
} from './constants.js';
import { old_values } from './reactivity/sources.js';
import {
@ -671,6 +672,13 @@ export function get(signal) {
if (is_dirty(derived)) {
update_derived(derived);
} else if ((derived.f & HAS_EFFECTS) !== 0 && derived.effects === null) {
// Recreate effects they have been destroyed without turning the derived dirty.
// Clear flag first in case the derived would now no longer create an effect
// because it's executing a different if-branch for example. Will be readded
// via create_effect if there turns out to be one.
derived.f ^= HAS_EFFECTS;
update_derived(derived);
}
}

@ -1441,4 +1441,50 @@ describe('signals', () => {
assert.deepEqual(log, ['inner destroyed', 'inner destroyed']);
};
});
test('derived effects reconnect correctly', () => {
const log: string[] = [];
let a: Derived<number>;
return () => {
const destroy1 = effect_root(() => {
a = derived(() => {
user_effect(() => {
log.push('effect-executed');
});
return 42;
});
});
const destroy2 = effect_root(() => {
render_effect(() => {
$.get(a);
});
});
assert.equal(log.length, 0);
assert.equal(a?.effects?.length, 1);
destroy2();
flushSync();
assert.equal(a?.effects, null);
assert.equal(log.length, 0);
const destroy3 = effect_root(() => {
render_effect(() => {
const value = $.get(a);
assert.equal(value, 42);
});
});
flushSync();
assert.equal(log.length, 1);
assert.equal(a?.effects?.length, 1);
destroy3();
destroy1();
};
});
});

Loading…
Cancel
Save