fix: make untrack behave correctly in relation to mutations

fix-untrack-expose-unsafe
Dominic Gannaway 3 months ago
parent 999b92d134
commit ed802760ed

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make untrack behave correctly in relation to mutations

@ -187,7 +187,8 @@ export {
hasContext, hasContext,
setContext, setContext,
tick, tick,
untrack untrack,
unsafe
} from './internal/client/runtime.js'; } from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

@ -14,7 +14,8 @@ export {
noop as afterUpdate, noop as afterUpdate,
noop as onMount, noop as onMount,
noop as flushSync, noop as flushSync,
run as untrack run as untrack,
run as unsafe
} from './internal/shared/utils.js'; } from './internal/shared/utils.js';
export function createEventDispatcher() { export function createEventDispatcher() {

@ -16,7 +16,8 @@ import {
set_is_flushing_effect, set_is_flushing_effect,
set_signal_status, set_signal_status,
untrack, untrack,
skip_reaction skip_reaction,
untracking
} from '../runtime.js'; } from '../runtime.js';
import { import {
DIRTY, DIRTY,
@ -164,7 +165,7 @@ function create_effect(type, fn, sync, push = true) {
* @returns {boolean} * @returns {boolean}
*/ */
export function effect_tracking() { export function effect_tracking() {
if (active_reaction === null) { if (active_reaction === null || untracking) {
return false; return false;
} }

@ -18,7 +18,8 @@ import {
set_derived_sources, set_derived_sources,
check_dirtiness, check_dirtiness,
set_is_flushing_effect, set_is_flushing_effect,
is_flushing_effect is_flushing_effect,
unsafe_mutations
} from '../runtime.js'; } from '../runtime.js';
import { equals, safe_equals } from './equality.js'; import { equals, safe_equals } from './equality.js';
import { import {
@ -147,6 +148,7 @@ export function mutate(source, value) {
export function set(source, value) { export function set(source, value) {
if ( if (
active_reaction !== null && active_reaction !== null &&
!unsafe_mutations &&
is_runes() && is_runes() &&
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 && (active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
// If the source was created locally within the current derived, then // If the source was created locally within the current derived, then

@ -78,6 +78,10 @@ let dev_effect_stack = [];
/** @type {null | Reaction} */ /** @type {null | Reaction} */
export let active_reaction = null; export let active_reaction = null;
export let untracking = false;
export let unsafe_mutations = false;
/** @param {null | Reaction} reaction */ /** @param {null | Reaction} reaction */
export function set_active_reaction(reaction) { export function set_active_reaction(reaction) {
active_reaction = reaction; active_reaction = reaction;
@ -387,6 +391,8 @@ export function update_reaction(reaction) {
var previous_skip_reaction = skip_reaction; var previous_skip_reaction = skip_reaction;
var prev_derived_sources = derived_sources; var prev_derived_sources = derived_sources;
var previous_component_context = component_context; var previous_component_context = component_context;
var previous_untracking = untracking;
var previous_unsafe_mutations = unsafe_mutations;
var flags = reaction.f; var flags = reaction.f;
new_deps = /** @type {null | Value[]} */ (null); new_deps = /** @type {null | Value[]} */ (null);
@ -396,6 +402,8 @@ export function update_reaction(reaction) {
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0; skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
derived_sources = null; derived_sources = null;
component_context = reaction.ctx; component_context = reaction.ctx;
untracking = false;
unsafe_mutations = false;
try { try {
var result = /** @type {Function} */ (0, reaction.fn)(); var result = /** @type {Function} */ (0, reaction.fn)();
@ -434,6 +442,8 @@ export function update_reaction(reaction) {
skip_reaction = previous_skip_reaction; skip_reaction = previous_skip_reaction;
derived_sources = prev_derived_sources; derived_sources = prev_derived_sources;
component_context = previous_component_context; component_context = previous_component_context;
untracking = previous_untracking;
unsafe_mutations = previous_unsafe_mutations;
} }
} }
@ -856,7 +866,7 @@ export function get(signal) {
} }
// Register the dependency on the current reaction signal. // Register the dependency on the current reaction signal.
if (active_reaction !== null) { if (active_reaction !== null && !untracking) {
if (derived_sources !== null && derived_sources.includes(signal)) { if (derived_sources !== null && derived_sources.includes(signal)) {
e.state_unsafe_local_read(); e.state_unsafe_local_read();
} }
@ -1016,12 +1026,31 @@ export function invalidate_inner_signals(fn) {
* @returns {T} * @returns {T}
*/ */
export function untrack(fn) { export function untrack(fn) {
const previous_reaction = active_reaction; var previous_untracking = untracking;
try { try {
active_reaction = null; untracking = true;
return fn(); return fn();
} finally { } finally {
active_reaction = previous_reaction; untracking = previous_untracking;
}
}
/**
* When used inside a [`$derived`](https://svelte.dev/docs/svelte/$derived),
* any state updates to state is allowed.
*
* ```
* @template T
* @param {() => T} fn
* @returns {T}
*/
export function unsafe(fn) {
var previous_unsafe_mutations = unsafe_mutations;
try {
unsafe_mutations = true;
return fn();
} finally {
unsafe_mutations = previous_unsafe_mutations;
} }
} }

@ -1,5 +1,5 @@
/** @import { Readable } from './public' */ /** @import { Readable } from './public' */
import { untrack } from '../index-client.js'; import { unsafe } from '../index-client.js';
import { noop } from '../internal/shared/utils.js'; import { noop } from '../internal/shared/utils.js';
/** /**
@ -22,7 +22,7 @@ export function subscribe_to_store(store, run, invalidate) {
// Svelte store takes a private second argument // Svelte store takes a private second argument
// StartStopNotifier could mutate state, and we want to silence the corresponding validation error // StartStopNotifier could mutate state, and we want to silence the corresponding validation error
const unsub = untrack(() => const unsub = unsafe(() =>
store.subscribe( store.subscribe(
run, run,
// @ts-expect-error // @ts-expect-error

@ -1,5 +1,5 @@
<script> <script>
import { untrack } from 'svelte'; import { unsafe } from 'svelte';
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
const cache = new SvelteMap(); const cache = new SvelteMap();
@ -13,7 +13,7 @@
cache.set(id, id.toString()); cache.set(id, id.toString());
}).then(() => cache.get(id)); }).then(() => cache.get(id));
untrack(() => { unsafe(() => {
cache.set(id, promise); cache.set(id, promise);
}); });
@ -25,7 +25,7 @@
const value = $derived(get_async(1)); const value = $derived(get_async(1));
const value2 = $derived(get_async(1)); const value2 = $derived(get_async(1));
// both values are read before the set // both values are read before the set
value; value;
value2; value2;
</script> </script>

@ -1,9 +1,9 @@
<script> <script>
import { untrack } from 'svelte'; import { unsafe, untrack } from 'svelte';
let count = $state(0); let count = $state(0);
function default_arg() { function default_arg() {
untrack(() => count++); untrack(() => unsafe(() => count++));
return 1; return 1;
} }
</script> </script>

@ -1,9 +1,9 @@
<script> <script>
import { untrack } from 'svelte'; import { unsafe, untrack } from 'svelte';
let count = $state(0); let count = $state(0);
function default_arg() { function default_arg() {
untrack(() => count++); untrack(() => unsafe(() => count++));
return 1; return 1;
} }
</script> </script>

@ -484,6 +484,13 @@ declare module 'svelte' {
* ``` * ```
* */ * */
export function untrack<T>(fn: () => T): T; export function untrack<T>(fn: () => T): T;
/**
* When used inside a [`$derived`](https://svelte.dev/docs/svelte/$derived),
* any state updates to state is allowed.
*
* ```
* */
export function unsafe<T>(fn: () => T): T;
/** /**
* Retrieves the context that belongs to the closest parent component with the specified `key`. * Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation. * Must be called during component initialisation.

Loading…
Cancel
Save