Reactive blocks run only once per tick, losing changes and allowing fast pace apps to get out of sync
See original GitHub issueDescribe the bug
TLDR: Reactive blocks that need to stay up to speed with multiple state changes during the same tick basically break the application in subtle, hard to reason about ways (because things “randomly” go out of sync). This bug breaks the basic guarantee of reactivity.
- This bug goes all the way back to version 3.0.0.
- I am giving it high severity because at least for us, it is a huge factor in deciding whether to use Svelte for future projects and I think it will be the same for other teams the encounter these behaviours.
More Info and context (skip to the next section for REPLs):
- My team is using Svelte in production to build some highly graphic and complex interactive experiences (game like), which sometimes mean rapid state updates and ideally a lot of reactive blocks to keep different pieces separate.
- This bug happens with stores as well, even though store subs behave differently as they do fire multiple times per tick (which is a good thing). This adds to the confusion and overall feeling of inconsistency (people in my team initially thought stores are causing this issue).
- Anything async also behaves differently (because it is not in the same tick), it leads to consistent state and potentially introduces infinite loops in a surprising manner (because the code “worked just fine” because of this bug when there was no async behaviour) which adds to the confusion and inconsistency of the DX.
- Because of how puzzling it is when it happens in a complex app (I change state but it doesn’t render), devs in my team were constantly puzzled by why one way of doing something works while another sends the app to hell. We started thinking about reactive blocks as strange foot guns 😢 . Now that I know the root cause we can find (ugly) ways around it by being extra vigilant at all times, but I am sure others will encounter it too.
- I was told by some on #6730 (I thought it is the same issue at first) that this is a built in protection from possible infinite loops. If that’s the case this is at minimum a bug in the documentation and in my opinion not a good design decision for the framework. App consistency is more important and this defence mechanism is very limited and confusing anyway. It also means svelte is not well suited for the type of apps that could benefit the most from its full feature-set, performance, small size and elegance (== complex apps built by devs who can easily defend from infinite loops with normal code or the conditionals of the reactive blocks).
- I know that changing this can break existing apps that rely on it. Could this behaviour be made opt-out via some compiler option or otherwise (Ideally something we can pass in via the rollup config)?
- I am willing to allocate resources to fixing it if that helps (and if we can get some guidance for where to start from)
Reproduction
Expected behaviour: isSmallerThan10 should be false. Actual behaviour: isSmallerThan10 stays true, which is out of sync with the app state (and breaks the contract of reactivity)
Logs
N/A
System Info
System:
OS: macOS 11.5.2
CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Memory: 39.63 GB / 64.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 12.16.2 - ~/.nvm/versions/node/v12.16.2/bin/node
Yarn: 1.22.10 - ~/.nvm/versions/node/v12.16.2/bin/yarn
npm: 7.16.0 - ~/.nvm/versions/node/v12.16.2/bin/npm
Browsers:
Brave Browser: 86.1.15.76
Chrome: 93.0.4577.82
Firefox: 89.0.2
Safari: 14.1.2
Severity
blocking all usage of svelte
Issue Analytics
- State:
- Created 2 years ago
- Reactions:9
- Comments:35 (21 by maintainers)
Top Results From Across the Web
Screen and Block Lifecycle Events - OutSystems documentation
In OutSystems Mobile and Reactive Web Apps, the Screens and Blocks follow a lifecycle composed by a set of stages.
Read more >Advanced Svelte: Reactivity, lifecycle, accessibility
In this article we will add the app's final features and further componentize our app. We will learn how to deal with reactivity...
Read more >Unreal Engine 5.1 Release Notes
Overview of new and updated features in Unreal Engine 5.1.
Read more >Leading Blog: A Leadership Blog - LeadershipNow.com
STUDIES have shown that humility is one of the most important ... unable to get out of the funk we are in, we...
Read more >The Brain That Changes Itself - BrainMaster
Neuro is for "neuron," the nerve cells in our brains and nervous systems. Plastic is for "changeable, malleable, modifiable." At first many of...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Note that your REPL example can be simplified even further: the same behavior is seen without needing an object property. In other words, when you write
let count = {a:1};
, you could demonstrate the same behavior even more simply withlet count = 1
, as follows:The docs say that
onDestroy
“Schedules a callback to run immediately before the component is unmounted.” So you can put it inside a helper function and that’s okay, because when that helper function is being run, it’s always being run by a component, and it will add that callback to that component’sonDestroy
list.