How to avoid cyclic dependencies without battling with the compiler in svelte?
Asked Answered
M

2

10

I was reading the doc, and after tweaking its sample code, I managed to get compiler barked at me about cyclic dependencies like this:

<script>
    let count = 0;
    $: double = count * 2;

    $: if (double >= 20) {
        alert(`count is dangerously high!`);
        count = 9;
    }

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

I asked on discord how to fix it, and people suggested that I should hide the dependencies from the compiler like this:

<script>
    let count = 0;
    $: double = count * 2;

    function resetCount() {
        count = 9;
    }

    $: if (double >= 20) {
        alert(`count is dangerously high!`);
        resetCount();
    }

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

It works, but I got a couple questions:

  1. Battling with the compiler doesn't sound right to me, is there any other better ways to fix this?
  2. A more general question is that does cyclic dependencies happen quite often to people who have written large amount of svelte code? Is it normal or it usually signals a bad design?

Thanks.

Molly answered 11/7, 2019 at 13:50 Comment(0)
C
10

The answer from @morphyish sort-of provides a fix, because as they stated:

a reactive statement can't trigger itself

However, I see that as somewhat of a technicality, and still see the provided solution as conceptually having a cyclic dependency: we still have count -> double -> count -> ....

And because we've bypassed this cyclic dependency warning by merging the statements into a single reactive block, we've actually also introduced a bug:

enter image description here

This bug occurs because the double value is set to 10 * 2 = 20 at the beginning of the reactive block, then count is set to 9 within the if-statement, but then double is not set back to 9 * 2 = 18 because the reactive block doesn't trigger again.

My suggestion in this, and similar cases would be to re-evaluate what your dependencies actually are in order to remove these cycles:

double = count * 2;

^ So double depends on count, that one's easy.

if (double >= 20) {
  alert('count is dangerously high!');
  count = 9;
}

^ At first glance it might seem like our count-reset logic depends on double, but seeing as we've already established that double depends on count – and this block is ultimately concerned with count – this logic really depends on count not double.

So in my view, the best solution would be to modify the conditional to match the actual dependency:

<script>
    let count = 0;
    $: double = count * 2;

    $: if (count >= 10) {
        alert(`count is dangerously high!`);
        count = 9;
    }

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

Cupro answered 12/8, 2019 at 6:25 Comment(0)
B
3

You could fix this issue by organizing your code slightly differently:

<script>
    let count = 0;
    let double;
    $: {
       double = count * 2;
       if (double >= 20) {
        alert(`count is dangerously high!`);
        count = 9;
       }
    }

    function handleClick() {
        count += 1;
    }
</script>

<button on:click={handleClick}>
    Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

You can group reactive statements together using {} with a small caveat: Svelte won't automatically write the variable declaration as it would otherwise.

I've never run into this issue before, but in your case it looks like both statement are dependent on count being updated, albeit indirectly for the second one. So it makes sense to actually group them into a single statement.

It also solves your issue as a reactive statement can't trigger itself.

However it means that if you want to also update double you would need to do it explicitly.

Bechtold answered 29/7, 2019 at 15:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.