fix: address bug in before/after update (#9448)

* fix: address bug in before/after update

fix: address bug in before/after update

* Add changeset

* use every instead of filter - more explicit and enables early-exit from the loop

* Update logic and comment

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/9464/head
Dominic Gannaway 1 year ago committed by GitHub
parent f5101c0d8c
commit 9eb969ddd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: corrects a beforeUpdate/afterUpdate bug

@ -960,10 +960,18 @@ export function set_signal_value(signal, value) {
schedule_effect(current_effect, false); schedule_effect(current_effect, false);
} }
mark_signal_consumers(signal, DIRTY, true); mark_signal_consumers(signal, DIRTY, true);
// If we have afterUpdates locally on the component, but we're within a render effect // This logic checks if there are any render effects queued after the above marking
// then we will need to manually invoke the beforeUpdate/afterUpdate logic. // of consumers. If there are render effects that have the same component context as
// the source signal we're writing to, then we can bail-out of this logic as there
// will be a render effect in the queue that hopefully takes case of triggering the
// beforeUpdate/afterUpdate logic (doing it again here would duplicate them). However,
// if the render effects scheduled in the queue are unrelated to the component context,
// then we need to trigger the beforeUpdate/afterUpdate logic here instead.
// TODO: should we put this being a is_runes check and only run it in non-runes mode? // TODO: should we put this being a is_runes check and only run it in non-runes mode?
if (current_effect === null && current_queued_pre_and_render_effects.length === 0) { if (
current_effect === null &&
current_queued_pre_and_render_effects.every((e) => e.context !== component_context)
) {
const update_callbacks = component_context?.update_callbacks; const update_callbacks = component_context?.update_callbacks;
if (update_callbacks != null) { if (update_callbacks != null) {
update_callbacks.before.forEach(/** @param {any} c */ (c) => c()); update_callbacks.before.forEach(/** @param {any} c */ (c) => c());

@ -0,0 +1,7 @@
<script>
const {count, increment} = $props();
</script>
<button onclick={increment}>
{count}
</button>

@ -0,0 +1,15 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: '<button>0</button>',
async test({ assert, target, component }) {
const [btn] = target.querySelectorAll('button');
flushSync(() => {
btn.click();
});
assert.deepEqual(component.log, ['beforeUpdate', 'afterUpdate']);
assert.htmlEqual(target.innerHTML, `<button>1</button>`);
}
});

@ -0,0 +1,22 @@
<script>
import Child from './Child.svelte'
import {afterUpdate, beforeUpdate} from 'svelte';
const {log = []} = $props();
let count = $state(0);
const increment = () => {
count++;
}
beforeUpdate(() => {
log.push('beforeUpdate');
});
afterUpdate(() => {
log.push('afterUpdate');
});
</script>
<Child count={count} increment={increment} />
Loading…
Cancel
Save