Skip to content

Svelte 5: Fine grained reactivity when binding directly to array elements. #9687

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
joaquimnetocel opened this issue Nov 28, 2023 · 8 comments

Comments

@brunnerh
Copy link
Member

brunnerh commented Nov 28, 2023

You bind through the list and the index, which causes the list to be invalidated.
The generated code:

$.bind_value(
  input,
  () => $.get(todos)[0].text,
  ($$value) => $.mutate(todos, $.get(todos)[0].text = $$value)
);

Not sure if this will be changed in the future, but right now you can work around it by separating the item from the list first.

let todo0 = $derived(todos[0]);
let todo1 = $derived(todos[1]);
<input bind:value={todo0.text} />
<input bind:value={todo1.text} />

REPL

This now also will generate a warning on the list:

todos is declared with $state(...) but is never updated. Did you mean to create a function that changes its value?

@joaquimnetocel
Copy link
Author

Got it. But for me, it is weird binding on a derived state, since the code below does not work:

<script>
	let number = $state(3);
	let double = $derived(number*2);
</script>

{number}
{double}

<input bind:value={double}>

@brunnerh
Copy link
Member

brunnerh commented Nov 28, 2023

$derived variables themselves are read-only, so they cannot be used with bind: directly, which would re-assign them. In my example, the object referenced by the derived variable is changed, not the variable itself.

@joaquimnetocel
Copy link
Author

joaquimnetocel commented Nov 29, 2023

I think that maybe "passing by reference" versus "passing by value" is an explanation for your code. Take a look at this simple example with pure javascript:

let array = [{value:3}];
function extraction(){
	return array[0];
}
let extracted=extraction();

// HERE, extracted IS A REFERENCE TO {value:3}. NOT A COPY. 

// NOW, WHEN WE CHANGE extracted, THE ORIGINAL ARRAY WILL CHANGE.

extracted.value=50;
console.log(array[0].value) // SHOWS 50

I think that you could bind to the derived result, because it is a reference for the object inside the array (and not a copy). As a reference, it is the array element itself that you bind to. Do you agree?

@brunnerh
Copy link
Member

Well, not quite. It could be possible in theory, but that would introduce a special case for $derived.
$derived is very generic, there does not need to be any "way back to the source".

The classic example being:

let count = $state(0);
let doubled = $derived(count * 2);

You cannot just change doubled and have that magically change count.

What you are suggesting has been suggested before in the form of something like a $proxy rune, that can only be a property/indexer path on another object and would behave like said path itself were used. Such a variable could be read and assigned.

@joaquimnetocel
Copy link
Author

Take a look at this REPL.

As you can see in the REPL above, updating the derived state changes the original array too. I understand it as a "way back to the source". It smells like a "passing by reference" instead of "passing by value".

In my point of view, the same doesn't happen in the example below because "number" is a primitive and, consequently, "doubled" is not a reference, but a complete new calculation.

let count = $state(0);
let doubled = $derived(count * 2);

@joaquimnetocel
Copy link
Author

joaquimnetocel commented Nov 29, 2023

It would be nice if we could do directly:

<input bind:value={todos[0]}>

@joaquimnetocel joaquimnetocel changed the title Svelte 5: Fine grained reactivity not working without an each block. Svelte 5: Fine grained reactivity when binding directly to array elements. Nov 29, 2023
@rmunn
Copy link
Contributor

rmunn commented Dec 7, 2023

Now that #9739 has been merged, I believe this issue can be closed as it now works the way you want it to. (At least, if I've understood your reproduction correctly).

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

4 participants