Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wicked-zebras-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: ensure SvelteDate cached methods have correct reactive context
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function listen(target, events, handler, call_handler_immediately = true)
/**
* @template T
* @param {() => T} fn
* @returns {T}
*/
export function without_reactive_context(fn) {
var previous_reaction = active_reaction;
Expand Down
47 changes: 38 additions & 9 deletions packages/svelte/src/reactivity/date.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
/** @import { Source } from '#client' */
import { DESTROYED } from '../internal/client/constants.js';
/** @import { Source, Derived, Effect } from '#client' */
import { DERIVED } from '../internal/client/constants.js';
import { derived } from '../internal/client/index.js';
import { source, set } from '../internal/client/reactivity/sources.js';
import { get } from '../internal/client/runtime.js';
import { active_reaction, get, set_active_reaction } from '../internal/client/runtime.js';

var inited = false;

/**
* @template T
* @param {() => T} fn
* @returns {T}
*/
function with_parent_effect(fn) {
var previous_reaction = active_reaction;
var parent_reaction = active_reaction;

while (parent_reaction !== null) {
if ((parent_reaction.f & DERIVED) === 0) {
break;
}
parent_reaction = /** @type {Derived | Effect} */ (parent_reaction).parent;
}

set_active_reaction(parent_reaction);
try {
return fn();
} finally {
set_active_reaction(previous_reaction);
}
}

export class SvelteDate extends Date {
#time = source(super.getTime());

Expand Down Expand Up @@ -43,12 +67,17 @@ export class SvelteDate extends Date {

var d = this.#deriveds.get(method);

if (d === undefined || (d.f & DESTROYED) !== 0) {
d = derived(() => {
get(this.#time);
// @ts-ignore
return date_proto[method].apply(this, args);
});
if (d === undefined) {
// Ensure we create the derived inside the nearest parent effect if
// we're inside a derived, otherwise the derived will be destroyed each
// time it re-runs
d = with_parent_effect(() =>
derived(() => {
get(this.#time);
// @ts-ignore
return date_proto[method].apply(this, args);
})
);

this.#deriveds.set(method, d);
}
Expand Down
30 changes: 30 additions & 0 deletions packages/svelte/src/reactivity/date.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,3 +642,33 @@ test('Date methods invoked for the first time in a derived', () => {

cleanup();
});

test('Date methods shared between deriveds', () => {
const date = new SvelteDate(initial_date);
const log: any = [];

const cleanup = effect_root(() => {
const year = derived(() => {
return date.getFullYear();
});
const year2 = derived(() => {
return date.getTime(), date.getFullYear();
});

render_effect(() => {
log.push(get(year) + '/' + get(year2).toString());
});

flushSync(() => {
date.setFullYear(date.getFullYear() + 1);
});

flushSync(() => {
date.setFullYear(date.getFullYear() + 1);
});
});

assert.deepEqual(log, ['2023/2023', '2024/2024', '2025/2025']);

cleanup();
});
Loading