Description
Describe the problem
I’d like to suggest to add a few more details to the docs around the “When not to use $effect
” section — particularly the "money spent / money left" example.
The current recommendation is to remove two $effect
s (which makes perfect sense!), and instead use two $state
variables (spent
and left
) that are kept in sync manually.
The $derived docs says that derived values can be overridden — which makes me wonder: is there a specific reason why$derived
is not used in this case? Is it bad practice? Perhaps it's related to how Svelte handles reactivity or performance under the hood?
Describe the proposed solution
In the case of two-way bound sliders:
<label>
<input type="range" bind:value={() => spent, updateSpent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={() => left, updateLeft} max={total} />
{left}/{total} left
</label>
What is intuitive for me is to write it with a derived value:
<script>
const total = 100; // use const to convey intention
let spent = $state(0);
let left = $derived(total - spent);
function updateSpent(newSpent) {
spent = newSpent;
}
function updateLeft(newLeft) {
spent = total - newLeft;
}
</script>
However, the recommendation in the docs is to use two states and keeps them in sync manually:
(Playground Example From the Docs)
<script>
let total = 100;
let spent = $state(0);
let left = $state(total);
function updateSpent(value) {
spent = value;
left = total - spent;
}
function updateLeft(value) {
left = value;
spent = total - left;
}
</script>
Could the docs clarify if it is ok to use $derived
in above scenario and in which scenarios should manually syncing $state
be preferred? Maybe highlighting the trade-offs (e.g. reactivity, clarity, potential pitfalls) would really help newcomers like me build accurate mental models around Svelte’s reactivity patterns.
Thanks for all all the amazing work the team has put in — the new reactivity model is genuinely a joy to use!
Update: Best Solution
<script>
const total = 100; // use const to convey intention
let spent = $state(0);
let left = $derived(total - spent);
function updateLeft(newLeft) {
spent = total - newLeft;
}
</script>
<label>
<input type="range" bind:value={spent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={() => left, updateLeft} max={total} />
{left}/{total} left
</label>
Importance
nice to have