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,
setContext,
tick,
untrack
untrack,
unsafe
} from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

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

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

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

@ -78,6 +78,10 @@ let dev_effect_stack = [];
/** @type {null | Reaction} */
export let active_reaction = null;
export let untracking = false;
export let unsafe_mutations = false;
/** @param {null | Reaction} reaction */
export function set_active_reaction(reaction) {
active_reaction = reaction;
@ -387,6 +391,8 @@ export function update_reaction(reaction) {
var previous_skip_reaction = skip_reaction;
var prev_derived_sources = derived_sources;
var previous_component_context = component_context;
var previous_untracking = untracking;
var previous_unsafe_mutations = unsafe_mutations;
var flags = reaction.f;
new_deps = /** @type {null | Value[]} */ (null);
@ -396,6 +402,8 @@ export function update_reaction(reaction) {
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
derived_sources = null;
component_context = reaction.ctx;
untracking = false;
unsafe_mutations = false;
try {
var result = /** @type {Function} */ (0, reaction.fn)();
@ -434,6 +442,8 @@ export function update_reaction(reaction) {
skip_reaction = previous_skip_reaction;
derived_sources = prev_derived_sources;
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.
if (active_reaction !== null) {
if (active_reaction !== null && !untracking) {
if (derived_sources !== null && derived_sources.includes(signal)) {
e.state_unsafe_local_read();
}
@ -1016,12 +1026,31 @@ export function invalidate_inner_signals(fn) {
* @returns {T}
*/
export function untrack(fn) {
const previous_reaction = active_reaction;
var previous_untracking = untracking;
try {
active_reaction = null;
untracking = true;
return fn();
} 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 { untrack } from '../index-client.js';
import { unsafe } from '../index-client.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
// StartStopNotifier could mutate state, and we want to silence the corresponding validation error
const unsub = untrack(() =>
const unsub = unsafe(() =>
store.subscribe(
run,
// @ts-expect-error

@ -1,5 +1,5 @@
<script>
import { untrack } from 'svelte';
import { unsafe } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';
const cache = new SvelteMap();
@ -13,7 +13,7 @@
cache.set(id, id.toString());
}).then(() => cache.get(id));
untrack(() => {
unsafe(() => {
cache.set(id, promise);
});
@ -25,7 +25,7 @@
const value = $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;
value2;
</script>

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

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

@ -484,6 +484,13 @@ declare module 'svelte' {
* ```
* */
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`.
* Must be called during component initialisation.

Loading…
Cancel
Save