I have two variables a
and b
which add up to 100. How do I set up a reactive declaration such that when a
changes b
changes to be 100 - a
and vice versa? When I try something like
let total = 100;
$: a = total - b;
$: b = total - a;
I get a 'Cyclical dependency detected' error. Is there any way to get this done?
The problem comes from the fact that Svelte wants to sort the reactive blocks in the order they depend upon each other: it wants to compute those that are depended upon but have no dependencies first, to avoid unneeded computation... or getting trapped in a loop.
Furthermore, Svelte considers a dependency of a reactive expression any reactive variable that appear in it.
And so, the actual source of your error is that both variables a
and b
appears in both reactive expressions.
The solution is to remove from the reactive expressions the unneeded variable, that is the one that is assigned to. This can be done by moving the assignment to a function outside of the reactive block. Unfortunately, it's more verbose...
<script>
let total = 100;
let a = 0
let b = 0
const setA = () => {
// the assignment to a (or b in the other function) are still
// reactive, of course, but Svelte won't propagate changes to
// variable that are assigned their current value, so this
// will break the loop
a = total - b
}
const setB = () => {
b = total - a
}
$: setA(total - b);
$: setB(total - a);
</script>
<pre>
a: {a}
b: {b}
</pre>
<label>
a <input type="number" bind:value={a} />
</label>
<label>
b <input type="number" bind:value={b} />
</label>
Edit
As remarked by @avf in the comments, the above code is misleading and not so obvious for the reader of the code. I wrote it like this at the time because I was focused on demonstrating the principles of Svelte reactivity.
In a real world context, I would rather recommend any of the following two forms.
Whenever possible, favour obviousness:
// this function is just normal
const setA = (value) => {
a = value
}
// this reactive expression makes it obvious and straightforward that
// its dependencies are total and b, and only those
$: setA(total - b)
When obviousness is not an option (for example because the content of the function would be more side effect than mere assignment), I'd rely on the following form that is very idiomatic to Svelte. Unfortunately, it's also very obscure to people who are not familiar to Svelte usually... But I guess that just belongs to those things you've got to learn to be really proficient with the framework.
const recomputeEverythingOrWhatever = () => {
...
}
// in Svelte lingo, this is broadly understood as "whenever a, or b, or
// total changes, then recompute everything (or whatever)"
$: a, b, total, recomputeEverythingOrWhatever()
This syntax is fine IMO because it is generally familiar and well understood by Svelte developers.
Be careful however with what you put in the recompute function. If it changes the values of the dependencies of the reactive block, the code will compile fine this time, and Svelte would even break update loops at runtime to avoid freezing the JS thread... But you might still end up with code that will be very hard to comprehend or predict.