Open ivan-lednev opened 3 months ago
I don't understand how to rewrite this using runes.
You don't have to. Runes are not a replacement for Svelte 4 stores, they work fine together. It would be nice to have async function support for derived stores, though.
Runes are a replacement, but stores are not being deprecated yet. See:
Runes are an additive feature, but they make a whole bunch of existing concepts obsolete: ...
- the store API and
$
store prefix (while stores are no longer necessary, they are not being deprecated)
Using
$effect
to synchronize state this way seems like an anti-pattern, not to mention the fact that since my state logic is outside the component tree, and I'd have to manage cleanup myself with$effect.root
.
For asynchronous things, using $effect
is, as far as I know, currently the expected way to do it.
If you have state outside the component lifecycle, that sounds more like an issue to me since it points towards global state which is rarely a good idea and an outright hazard if you are using server-side rendering.
Simple example:
export function getAsync(init, compute) {
let val = $state(init);
$effect(() => {
compute().then(result => {
if (result != getAsync.valueChanged)
val = result;
});
})
return () => val;
}
getAsync.valueChanged = Symbol('value changed');
let value = $state(1);
let derivedValue = $derived.by(getAsync(
undefined,
async () => {
// dependencies need to be read before first `await` happens
const currentValue = value;
const result = await fetchData(currentValue);
if (currentValue !== value)
return getAsync.valueChanged;
return result;
},
));
The reading of the dependencies before async code could be further enforced by having an API that splits the logic into reading and calculating. E.g. with a signature like this:
function getAsync(
init: T,
dependencies: () => D,
compute: (dependencies: D) => Promise<T>,
): () => T;
@brunnerh yes, I phrased my comment vaguely, but I meant that stores are not deprecated yet.
Furthermore, I noticed it's possible to return a Promise from $derived.by or pass an async function to it, thus avoiding $effect.
E.g.
async function asyncFetch(search: string) {
// do some fetching
}
let value = $state('') // will be used as an initial value
let derivedValue = $derived.by(() => {
return asyncFetch(value)
})
// or
let derivedValue = $derived.by(async () => {
const data = await asyncFetch(value)
// do some transformations on data
return transform(data)
})
{#await derivedValue}
<div>Loading...</div>
{:then value}
{value}
{/await}
This is similar to what is achieved with createResource in SolidJS.
Describe the problem
In my Svelte 4 app, I keep all the logic inside stores and away from components. I found this approach to be more predictable and bug-free.
Here is an example of a derived store that updates whenever the user changes the settings, that relies on
derived(v, (v, set) => { ... })
. I don't understand how to rewrite this using runes.Using
$effect
to synchronize state this way seems like an anti-pattern, not to mention the fact that since my state logic is outside the component tree, and I'd have to manage cleanup myself with$effect.root
.I've searched through the new docs, and I haven't encountered the word
async
there 😢.Describe the proposed solution
Solid.js has an
createResource
: https://docs.solidjs.com/reference/basic-reactivity/create-resource , which is exactly this: a signal that updates with an async function whenever its deps update.Importance
would make my life easier