sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.72k stars 4.13k forks source link

Unexpected results using `$effect` & `untrack` (circular dependecy) #13044

Closed catinrage closed 1 month ago

catinrage commented 1 month ago

Describe the bug

Hello guys,

My view on untrack function was such that if i use untrack inside an $effect or $derived rune all the states changes inside it would be ignored to other $effect and $dervied runes, and they would stop being triggered.

As showed in the provided snippet, i have 2 states called a & b which are both initially 0, my goal is to bind them both ways, meaning if a is changed the b would be updated accordingly and if b is updated a would be updated as well, but without falling into circular dependecy problem.

My workaround (atleast what i thought would work), was to create 2 $effects with each one being dependent to each a & b, and using untrack to stop the circular dependecy issue (please check snippet), at the result was not what i expected at all and it seems a little unpredictable to me, i guess i may have a incorrect idea about how it all works so i would appreciate any type of insight towards that.

Thanks in advance.

Reproduction

Reproduction Snippet

Logs

No response

System Info

System:
    OS: Linux 5.15 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700H
    Memory: 6.42 GB / 15.49 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 21.2.0 - ~/.nvm/versions/node/v21.2.0/bin/node
    Yarn: 1.22.22 - ~/.bun/bin/yarn
    npm: 10.8.1 - ~/.bun/bin/npm
    pnpm: 9.8.0 - ~/.nvm/versions/node/v21.2.0/bin/pnpm
    bun: 1.1.13 - ~/.bun/bin/bun
  Browsers:
    Chrome: 125.0.6422.141
  npmPackages:
    svelte: 5.0.0-next.203 => 5.0.0-next.203

Severity

annoyance

dummdidumm commented 1 month ago

It's different - untrack does prevent any reads from counting towards dependencies, not writes. Giving this the documentation label so we can make it a more clear in the docs.

catinrage commented 1 month ago

It's different - untrack does prevent any reads from counting towards dependencies, not writes. Giving this the documentation label so we can make it a more clear in the docs.

So writes will always be counted and will trigger the signal, am i correct ?

catinrage commented 1 month ago

Is there any way to achieve what i was trying to do in the issue ? (2 way state binding).

7nik commented 1 month ago

But the binding with two effects works.

Note that effects aren't synchronous. In your example, the following happens

  1. b is set to 10
  2. a is set to 3
  3. runs $effect.pre, but no such in the example
  4. markup updates
  5. runs $effect in the order they are defined 5.1. the first effect does a = b (10) 5.2. the second effect does b = a (they are already equal, so b doesn't change)
  6. runs $effect.pre (for changed a), but no such in the example
  7. markup updates
catinrage commented 1 month ago

@7nik Thanks for clarifications.

  1. runs $effect in the order they are defined

If $effects run in order they are defined i'd expect first effect to run first when i click a = 3, which make a to become 0 again, but in reality the second effect runs first, while the both are dependent to a, provided an example here :

snippet

The other possiblity is that svelte is smart enough to know that don't re-run an effect only if written states are chagned, in provided code a get's written in the first effect so there is no need to re-run it.

7nik commented 1 month ago

while the both are dependent to a

They aren't. $effects depend on only what they read (*) but not write: b for the first and a for the second.

*: in case of props, e.g. obj.foo.bar = 42, obj.foo is read, but .bar isn't so that the effect will subscribe to obj and obj.foo here. Also, obj.foo.bar += 1 does read obj.foo.bar.