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>
That does not work; Svelte does not allow this and this cycle cannot be resolved either. If you want a variable to be editable, you cannot declare it as reactive ($: x = ...). You can either reactively set regular variables with reactive statements/blocks or use events instead (see other answers).
The following is an explanation why this could not be resolved logically either.
Treating this like two equations with two unknowns you get this useless simplification:
a = total - b
b = total - a
b = total - (total - b)
b = total - total + b
b = b
You have to fix at least one of the values, otherwise all possible values are valid here.
You can also first normalize the equations:
a = total - b => a + b = total => a + b = total
b = total - a => b + a = total => a + b = total
As you can see, they are the same, so you actually have two unknowns and only one equation, so this is underspecified.
(Note that even if this would yield a valid solution, Svelte cannot solve systems of linear equations for you.)
Unless you need the values to update from programmatic changes, i would drop the reactive statements entirely and work with events instead: When one variable is adjusted by the user, update the other one.
<script>
const total = 100;
let a = total;
let b = 0;
</script>
a: {a} <br>
b: {b}
<label>
a <input type="number" bind:value={a}
on:input={() => b = total - a} />
</label>
<label>
b <input type="number" bind:value={b}
on:input={() => a = total - b} />
</label>