Skip to content

Explicit dependencies for reactive statements + Syntax proposal or Docs update #5615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cdellacqua opened this issue Oct 30, 2020 · 10 comments
Closed

Comments

@cdellacqua
Copy link

Is your feature request related to a problem? Please describe.
This feature is related to the ambiguity of reactive statement inference methods that, by abstracting the process of determining the dependencies, can cause unintended behavior (more in the examples below). This feature is an addition to the syntax that enables users to explicitly signal which variables should trigger a reactive statement, without changing the current behavior of standard reactive statements.

Describe the solution you'd like
As per the example below, a possible solution can be the idiom:

function functionWithReactiveCode() {
  // ...
}
$: [array, of, watched, variables] && functionWithReactiveCode();

With this syntax simple values can be watched by the reactive statement, and, because an array always evaluates to true, any time one of the variables gets reassigned, it will be triggered independently of the variables read in the function code which won't affect the dependency graph computed by Svelte.

BUT if any of the watched variables is the property of an object, the statement gets wrongly triggered even if that particular property wasn't changed. Because changing the way the dependency graph works would be a major breaking change and probably confuse a lot of developers that are already familiar with the current behavior, I came up with another idea: we could create a specific syntax that explicitly states the dependencies of a reactive block, thus letting the compiler skip the automatic construction of the dependency graph for this kind of statements.
I can imagine something like the following:

$[array, of, watched, variables, and, obj.property]: {
  // reactive code that won't get analyzed for dependency.
  // The compiler will trust the developer intentions.
}

The square brackets can be adopted because they won't create ambiguity with jQuery-like function calls and also for symmetry with the cited idiom.

Describe alternatives you've considered
It's often the case that in a reactive statement a variable is read or written to or it is assumed it will be written to because of a nested structure, thus triggering related statements. A classic example of this behavior has been brought up numerous times in this repository with something like this:

<script>
  export let obj = { value: 'default' };
  let inputValue = '';
  $: inputValue = obj.value;
</script>
<input type="text" bind:value={inputValue} />

The example above results in a readonly input field, because obj is invalidated any time inputValue is. There are some ways to work around this problem, for example wrapping the reactive statement code in a function. Doing so, the example above becomes:

<script>
  export let obj = { value: 'default' };
  let inputValue = '';
  function reactiveCode(newValue) {
    inputValue = newValue;
  }
  $: reactiveCode(obj.value);
</script>
<input type="text" bind:value={inputValue} />

This fixes the reactivity problem by explicitly stating that the reactiveCode function should only be called when obj is changed.

Another way to explicitly state dependencies for a reactive block I've also found is this idiom:

<script>
  let flag = false;
  let color = '000000';
  function reactiveCode() {
    color = Math.random().toString(16).substring(2, 8);
  }
  $: [flag] && reactiveCode();
</script>
<input style="border: 1px solid #{color}" type="text" value="example" />
<button on:click={() => flag = !flag}>
  change
</button>

REPL: https://svelte.dev/repl/de48158c145a437e9cdf7f9896349354?version=3.29.4
In this example the reactive code does not depend on flag but the intended behavior is that whenever the flag variable changes, the border color must change. Moreover the reactive code is wrapped in a function so that even if there were other variables read in that code, they wouldn't affect the dependency graph.

Even if this syntax proposal gets rejected, I think that the idiom that already works should be added to the Svelte Docs and Tutorial so that anyone can take advantage of this behavior without having to figure out how reactivity is currently implemented for different types.

How important is this feature to you?
The new syntax per se is a really nice-to-have feature because it would solve different kinds of problems with very little verbosity when compared to the idiom. If instead the syntax gets rejected, I think it's really important to clearly state in the Tutorial and Docs how reactivity works when function calls are involved and present this example as a solution for use cases when a user wants to control which variables the code should react to.

Additional context
Related issues:

@kevmodrome
Copy link
Contributor

You can already sort-of do this:
$: flag, reactiveCode();

@Conduitry
Copy link
Member

$[array, of, watched, variables, and, obj.property]: {
  // reactive code that won't get analyzed for dependency.
  // The compiler will trust the developer intentions.
}

isn't valid JS syntax, and so isn't something we can use. As Kev mentions, you can already do this with a different syntax. Having a more in-depth guide/cookbook-type section on the site that discusses more details about how the reactivity works and how to achieve various results is something that's planned, and there's at least one issue for it somewhere.

@cdellacqua
Copy link
Author

As @kevmodrome mentioned there are a series of equivalent syntaxes to the one I presented in the example above that works in some cases, but that's not the point of the issue: using the comma operator or an array + && does not solve the problem of nested properties because the reactive statement gets triggered if any of the values an object contains changes, even if the one the reactive statement is "watching" stays the same, thus I think that the only way to implement this behavior is to create a specific syntax. That's the main reason, right now there is no way to make a reactive statement on a single object property without having to manually check if the property actually changed, which is a bit counter-intuitive.

About the "invalid JS syntax", it's not really a compelling argument considering that Svelte is a compiler and that even if $: is valid JS syntax it has completely been repurposed in a way that makes it inconsistent with what any JS engine would interpret. Same applies for the autosubscription, $ before a variable name shouldn't modify the behavior of that variable but yield "$something is not defined" or even "assignment to undeclared variable $something" considering that $ is a valid character in a variable name.

@cdellacqua
Copy link
Author

Here is a REPL demonstrating what I'm referring to:
https://svelte.dev/repl/239c2dccb5464004a5c7bcfcfb3d62a5?version=3.29.4

@stephane-vanraes
Copy link
Contributor

You can get around that by passing through an intermediate value, in a sense destructuring your object and only 'listening' to changes to specific props

$: prop2 = obj.prop2
$: if (prop2) {
  counter++;
}	

@cdellacqua
Copy link
Author

cdellacqua commented Oct 30, 2020

@stephane-vanraes only if prop2 is a primitive value, otherwise it still gets triggered, because the invalidation process that triggers reactive statements treats objects and primitive types differently. As before, a specific syntax that override the automatic dependency graph would solve this, but right now there are only workarounds. The one you suggest can work with nested properties only by walking down all the hierarchy until the one you want to watch is reached (assuming one needs to listen to that primitive property and not a whole nested object).
REPL: https://svelte.dev/repl/199e608752c24905a4dab9fbe49ac79f?version=3.29.4

@zhaoyao91
Copy link

zhaoyao91 commented Nov 27, 2020

You can already sort-of do this:
$: flag, reactiveCode();

further to reduce the naming problem:

$: flag, (() => {
  ...
})()

@mdynnl
Copy link

mdynnl commented Jan 4, 2021

further, maybe uglier 😐

$:{flag;{
  // ...
}}

@TerrapinSoftware
Copy link

Why not just use the $ label technique:

$name: {
}

This is a non-breaking change; the compiler would not have to be changed much, and the implementation could be hidden behind a writable().

Watching multiple variables would imply multiple statements, but the syntax is clean:

let one, two, three;

$one: ...;
$two: ...;
$three: ...;

@gyurielf
Copy link

gyurielf commented Nov 8, 2021

Typescript still cry for this syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants