File tree Expand file tree Collapse file tree 10 files changed +109
-7
lines changed
tests/runtime-runes/samples/effect-order-6 Expand file tree Collapse file tree 10 files changed +109
-7
lines changed Original file line number Diff line number Diff line change 1+ ---
2+ ' svelte ' : patch
3+ ---
4+
5+ fix: abort and reschedule effect processing after state change in user effect
Original file line number Diff line number Diff line change @@ -19,6 +19,7 @@ export const INSPECT_EFFECT = 1 << 18;
1919export const HEAD_EFFECT = 1 << 19 ;
2020export const EFFECT_HAS_DERIVED = 1 << 20 ;
2121export const EFFECT_IS_UPDATING = 1 << 21 ;
22+ export const USER_EFFECT = 1 << 22 ;
2223
2324export const STATE_SYMBOL = Symbol ( '$state' ) ;
2425export const LEGACY_PROPS = Symbol ( 'legacy props' ) ;
Original file line number Diff line number Diff line change 99 set_active_effect ,
1010 set_active_reaction
1111} from './runtime.js' ;
12- import { effect , teardown } from './reactivity/effects.js' ;
12+ import { create_user_effect , teardown } from './reactivity/effects.js' ;
1313import { legacy_mode_flag } from '../flags/index.js' ;
1414
1515/** @type {ComponentContext | null } */
@@ -153,7 +153,7 @@ export function pop(component) {
153153 var component_effect = component_effects [ i ] ;
154154 set_active_effect ( component_effect . effect ) ;
155155 set_active_reaction ( component_effect . reaction ) ;
156- effect ( component_effect . fn ) ;
156+ create_user_effect ( component_effect . fn ) ;
157157 }
158158 } finally {
159159 set_active_effect ( previous_effect ) ;
Original file line number Diff line number Diff line change @@ -33,7 +33,8 @@ import {
3333 MAYBE_DIRTY ,
3434 EFFECT_HAS_DERIVED ,
3535 BOUNDARY_EFFECT ,
36- STALE_REACTION
36+ STALE_REACTION ,
37+ USER_EFFECT
3738} from '#client/constants' ;
3839import { set } from './sources.js' ;
3940import * as e from '../errors.js' ;
@@ -199,11 +200,17 @@ export function user_effect(fn) {
199200 reaction : active_reaction
200201 } ) ;
201202 } else {
202- var signal = effect ( fn ) ;
203- return signal ;
203+ return create_user_effect ( fn ) ;
204204 }
205205}
206206
207+ /**
208+ * @param {() => void | (() => void) } fn
209+ */
210+ export function create_user_effect ( fn ) {
211+ return create_effect ( EFFECT | USER_EFFECT , fn , false ) ;
212+ }
213+
207214/**
208215 * Internal representation of `$effect.pre(...)`
209216 * @param {() => void | (() => void) } fn
@@ -216,7 +223,7 @@ export function user_pre_effect(fn) {
216223 value : '$effect.pre'
217224 } ) ;
218225 }
219- return render_effect ( fn ) ;
226+ return create_effect ( RENDER_EFFECT | USER_EFFECT , fn , true ) ;
220227}
221228
222229/** @param {() => void | (() => void) } fn */
Original file line number Diff line number Diff line change @@ -22,7 +22,8 @@ import {
2222 ROOT_EFFECT ,
2323 DISCONNECTED ,
2424 EFFECT_IS_UPDATING ,
25- STALE_REACTION
25+ STALE_REACTION ,
26+ USER_EFFECT
2627} from './constants.js' ;
2728import { flush_tasks } from './dom/task.js' ;
2829import { internal_set , old_values } from './reactivity/sources.js' ;
@@ -571,6 +572,8 @@ function flush_queued_effects(effects) {
571572
572573 if ( ( effect . f & ( DESTROYED | INERT ) ) === 0 ) {
573574 if ( check_dirtiness ( effect ) ) {
575+ var wv = write_version ;
576+
574577 update_effect ( effect ) ;
575578
576579 // Effects with no dependencies or teardown do not get added to the effect tree.
@@ -587,9 +590,19 @@ function flush_queued_effects(effects) {
587590 effect . fn = null ;
588591 }
589592 }
593+
594+ // if state is written in a user effect, abort and re-schedule, lest we run
595+ // effects that should be removed as a result of the state change
596+ if ( write_version > wv && ( effect . f & USER_EFFECT ) !== 0 ) {
597+ break ;
598+ }
590599 }
591600 }
592601 }
602+
603+ for ( ; i < length ; i += 1 ) {
604+ schedule_effect ( effects [ i ] ) ;
605+ }
593606}
594607
595608/**
Original file line number Diff line number Diff line change 1+ <script >
2+ import B from ' ./B.svelte' ;
3+
4+ let { boolean, closed } = $props ();
5+
6+ $effect (() => {
7+ console .log (boolean);
8+ });
9+ </script >
10+
11+ <B {closed } />
Original file line number Diff line number Diff line change 1+ <script >
2+ import { close } from ' ./Child.svelte' ;
3+
4+ let { closed } = $props ();
5+
6+ $effect (() => {
7+ if (closed) close ();
8+ });
9+ </script >
Original file line number Diff line number Diff line change 1+ <script module >
2+ let object = $state ();
3+
4+ export function open () {
5+ object = { boolean: true };
6+ }
7+
8+ export function close () {
9+ object = undefined ;
10+ }
11+ </script >
12+
13+ <script >
14+ let { children } = $props ();
15+ </script >
16+
17+ {#if object ?.boolean }
18+ <!-- error occurs here, this is executed when the if should already make it falsy -->
19+ {@render children (object .boolean )}
20+ {/if }
Original file line number Diff line number Diff line change 1+ import { flushSync } from 'svelte' ;
2+ import { test } from '../../test' ;
3+
4+ export default test ( {
5+ async test ( { assert, target, logs } ) {
6+ const [ open , close ] = target . querySelectorAll ( 'button' ) ;
7+
8+ flushSync ( ( ) => open . click ( ) ) ;
9+ flushSync ( ( ) => close . click ( ) ) ;
10+
11+ assert . deepEqual ( logs , [ true ] ) ;
12+ }
13+ } ) ;
Original file line number Diff line number Diff line change 1+ <script >
2+ import A from ' ./A.svelte' ;
3+ import Child , { open } from ' ./Child.svelte' ;
4+
5+ let closed = $state (false );
6+ </script >
7+
8+ <button onclick ={open }>
9+ open
10+ </button >
11+
12+ <button onclick ={() => closed = true }>
13+ close
14+ </button >
15+
16+ <hr >
17+
18+ <Child >
19+ {#snippet children (boolean )}
20+ <A {closed } {boolean } />
21+ {/ snippet }
22+ </Child >
23+
You can’t perform that action at this time.
0 commit comments