Replies: 2 comments 2 replies
-
You can use import { tick } from 'svelte';
export default function readable(initial_value, start) {
let value = $state(initial_value);
let subscribers = 0;
let stop = null;
return {
get value() {
if ($effect.active()) {
$effect(() => {
if (subscribers++ === 0) {
stop = start(fn => value = fn(value));
}
return () => {
tick().then(() => {
if (--subscribers === 0) {
stop?.();
stop = null;
}
});
};
});
}
return value;
}
};
} |
Beta Was this translation helpful? Give feedback.
-
I've seen variants of the pattern linked by brunnerh cited as the go-to pattern in several PRs and issues. I've refactored it slightly into Usage 1export function makeClock() {
let date1 = $state(new Date());
let date2 = $state(new Date());
let date3 = $state(new Date());
const setupDate1 = () => {
const intervalId = setInterval(() => (date1 = new Date()), 1000);
return () => clearInterval(intervalId);
};
const setupDate2 = () => {
const intervalId = setInterval(() => (date2 = new Date()), 2000);
return () => clearInterval(intervalId);
};
const setupDate3 = () => {
const intervalId = setInterval(() => (date3 = new Date()), 3000);
return () => clearInterval(intervalId);
};
const watcher = createKeyedWatcher();
return {
get date1() {
watcher.watch(setupDate1);
return date1;
},
get date2() {
watcher.watch(setupDate2);
return date2;
},
get date3() {
watcher.watch(setupDate3);
return date3;
},
};
} Usage 2export function makeClock() {
let date1 = $state(new Date());
let date2 = $state(new Date());
let date3 = $state(new Date());
const sharedSetup = () => {
const intervalId1 = setInterval(() => (date1 = new Date()), 1000);
const intervalId2 = setInterval(() => (date2 = new Date()), 2000);
const intervalId3 = setInterval(() => (date3 = new Date()), 3000);
return () => {
clearInterval(intervalId1);
clearInterval(intervalId2);
clearInterval(intervalId3);
};
};
const watcher = createKeyedWatcher();
return {
get date1() {
watcher.watch(sharedSetup);
return date1;
},
get date2() {
watcher.watch(sharedSetup);
return date2;
},
get date3() {
watcher.watch(sharedSetup);
return date3;
},
};
} Implementationimport { tick, untrack } from "svelte";
function createKeyedWatcher() {
let watchers = new Map();
return {
watch(setup) {
if ($effect.tracking()) {
$effect(() => {
let entry = watchers.get(setup);
if (!entry) {
const cleanup = untrack(setup);
entry = [0, cleanup];
watchers.set(setup, entry);
}
entry[0]++;
return () => {
tick().then(() => {
entry[0]--;
if (entry[0] === 0) {
entry[1]?.(); // Run cleanup
watchers.delete(setup);
}
});
};
});
}
},
};
} This variant works better for tracking properties of classes, for example. I put emphasis on the keyed-ness in the name to detract people creating a new function with each property access. This: return {
get date() {
watcher.watch(() => {
const intervalId = setInterval(() => (date = new Date()), 1000);
return () => clearInterval(intervalId);
});
return date;
},
}; ...is probably not what you want in most cases. |
Beta Was this translation helpful? Give feedback.
-
I've been playing around with Svelte 5 by porting a project over, and one thing I'm not really seeing a good way to handle cleanup of side effects. For example, here's some runes code that creates a constantly-updating
Date
:In this example, how could I call
clearInterval()
once this reactive state is no longer being observed?For reference, here is how you could do this with stores:
I am aware stores are not going anywhere in Svelte 5. But is there a way to do this with runes, or is the word to keep using stores if you have side effects you need to manage?
Beta Was this translation helpful? Give feedback.
All reactions