Open KieranP opened 1 week ago
Hello,
Why did you replace onMount()
by $effect()
?
Why did you delay the loading of the CSS ?
Hello,
Why did you replace
onMount()
by$effect()
?Why did you delay the loading of the CSS ?
It seems that onMount is not going to be deprecated later
@adiguba Because the docs say that $effect is run when component is mounted, in otherwords, it is the same thing as onMount. And Svelte5 is encouraging the use of $effect.
As for the delayed CSS loading, the parent component has a theme selector, which when changed, sends the updated theme value to this child component, and loads the appropriate theme file.
onMount()
is only run when the component is mounted.
$effect()
is run when the component is mounted, AND when any of the reactive variable are changed.
I didn't see that you use a prop on your effect, but since you're not using the return value of import(), just remove the async/await :
$effect(() => {
import(`@styles/themes/${theme}.scss`)
})
And if your really need the return value, use .then()
(be carefull because code inside then()
will not be reactive).
Although @KieranP seems to be after $effect() for the wrong reasons on this particular instance, I'm with him in that $effect() should accept async functions. I know, this was removed because it causes confusion, etc. etc. Still, I think it is an overstep from Svelte, and the confusing cases should just be documented. My humble opinion.
This is a deliberate decision to discourage async within effects, because everything that is not synchronously read will not become a dependency of the effect.
Leaving this open to see how widespread this is and to see if people are aware of that gotcha but still want this - but it's unlikely we change it.
And Svelte5 is encouraging the use of $effect.
It's really not. There is an entire "When not to use effects" section in the docs because the potential for misuse is huge.
Arguably, onMount
should also not allow async functions.
The potential bugs is different, namely the cleanup function will not work.
onMount(async () => {
// ...
return () => clearInterval(handle); // does nothing
})
Maybe there is a way to just prevent this scenario via types, though?
If I remember correctly, back in the day when the types for $effect
allowed async functions, the documentation stated very clearly what was tracked: "and up to the first await.", I think it said. I understood well.
Anyway, you guys decide on this one. I was happy with async, now I'm not as happy, but maybe it's just me.
Can't you do something like this:
import { untrack } from 'svelte';
$effect(() => {
untrack(async () => {
await fetch("/");
})
});
I feel like when I'm thinking of converting onMount
to $effect
I think of it as converting to $effect
with an untrack
not just $effect
. It is a bit clunky but maybe we can have an $effect.once
to replace onMount
and it would be syntactic sugar to the above.
But why do you want to replace onMount()
?
One less import and its kind of annoying that its the last lifecycle function thats really left. I find it kind of jarring to have $effect
next to onMount
in the same file.
That is not a good reason, and there is still onDestroy
as well.
Well if onMount
was removed so would onDestroy
no? I get that its not a good reason its just a personal preference, I'm not saying that some syntactic sugar needs to be implemented.
onMount()
and $effect()
has two different usage...
Event if it's possible, I don't see any interest in a hacky code to simulate onMount with $effect...
Just use onMount()
directly...
Ok, so let's consider this use case (please ignore that there are obviously other ways to it, like using .then/.catch
, but it should demonstrate a valid use case people might want to accomplish). I can't use onMount because I want it to be triggered both on mount and when tab state is changed. But using await on the fetch is not possible unless the function is async. And function can't be async because effect doesn't allow it.
<script type="ts">
let container: HtmlDivElement | undefined = $state()
let tab = $state('dashboard')
$effect(() => {
const result = await fetch("/?tab=${tab}")
container?.innerHTML = result
})
</script>
<ul>
<li onclick={() => { tab = 'dashboard' }}>Dashboard</li>
<li onclick={() => { tab = 'settings' }}>Settings</li>
</ul>
<div bind:this={container}>
Loading...
</div>
You can simply use something like this :
async function loadTab(t) {
const result = await fetch("/?tab=${t}")
container?.innerHTML = result
}
$effect(() => {
loadTab(tab);
});
But I don't like this sort of stuff, as result can be incoherent with the state in case of network lags...
I think it's way better to use a $derived()
and an {#await}
to handle the promise.
Is not that $effect disallows async functions, is that its TypeScript has been made not to take it. If you ignore the TS error and write async code in your effect, it works.
To me, disallowing async code in effects feels like you're forcing me into declaring an extra function just for nothing. Why can't I have the more succinct syntax? This is my opinion on the matter.
Describe the bug
In most cases, I'm trying to replace onMount with $effect, which seems to be the better way to go. However, $effect doesn't allow an async function:
Tying Error:
Reproduction
See above
Logs
No response
System Info
Severity
annoyance