svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Spreading in the template isn't a potentially reactive operation

Open 7nik opened this issue 1 year ago • 13 comments

Describe the bug

<span>{ [...array] }</span>

is compiled as static text when the variable isn't marked as reactive.

Though I doubt people use it in the prod (at least it should be array.join(", ")), they can do it during development.

Reproduction

REPL

Logs

No response

System Info

Svelte 5.1.2

Severity

annoyance

7nik avatar Oct 25 '24 08:10 7nik

Unsure what you want to happen. Do you want arr2 to behave the same as arr1? If yes, your code needs to use $state() for arr2 as well.

<script>
	let arr1 = $state([]);
	let arr2 = $state(arr1);
</script>

The above works.

webJose avatar Oct 25 '24 08:10 webJose

You don't need to statify a stateful value. Also, in the original case, it was

let arr = getContect("myarr"); // returns stateful array

and it can be any other function.

7nik avatar Oct 25 '24 08:10 7nik

Ah, I think I see what confuses you. Assigning a stateful value to a variable doesn't carry the same effect. You see, runes tend to fool us by looking exactly like functions, but they don't work like functions. The reactive nature of a value is not carried over or copied by virtue of the assignment operator.

Someone at some point suggested to remove runes and instead do this syntax:

let state arr1 = []; // let arr1 = $state([]);
let arr2 = arr1; // What you have, but do you see the difference?

The $state() rune, no matter how identical-looking to a regular function seems to be, is not a function and does not produce an R-value. The effect of using $state is more of an L-value.

webJose avatar Oct 25 '24 08:10 webJose

In addition to what @webJose said see https://github.com/sveltejs/svelte/issues/13306 for related discussion (https://github.com/sveltejs/svelte/issues/13306#issuecomment-2358591827)

"Solution" is to use $derived https://svelte.dev/playground/hello-world?version=5.1.2#H4sIAAAAAAAAA22PwQqDMBBEfyWEHhRKpB6tCv2OmEPUbQ2NMSSrtEj-vcYe6qGXZXfezMCu9K40eFrwleLbAi12gZ6pkWO8btYyv4DGqLXSwz-9mwyCwa2Glr5zymLdmAY1IJHOXUhFTh4lQsJFej2QPJIenFqgT6Iz0jL7VZiynREnQybTadU9qzVJSVXvrczOfthTTIN54JCGWvY9UQhjmX1zdWxw24y2gvCVM8biLoI4oPyA8h1tTyG8kBboZggifABrk329KAEAAA==

Prinzhorn avatar Oct 25 '24 08:10 Prinzhorn

~Never mind, this actually looks like a bug:~ See edit

https://svelte.dev/playground/hello-world?version=5.1.2#H4sIAAAAAAAAA22PwQrCMBBEfyUEDy1I1B5rW_A7Yg5pXbWYpiHZihLy7yYtYg9eltmZtwPr6bVX4GjJPcW3AVrOBt1SLYe0nYxh7gkKk9dKB__8btQIGmMNrVxne4PNWZ9RARJp7YHUZONQImRc5MdVUsQkAdGrdr9DXbUT4qjJqDvVd4_aZzmpmxllZnL3bFYK9A3veWjk5UJ6hKHaLXdNarBxJqwk3HPGWNIiCOKT4HsRVlCxgoovVCxQfA_hhbREO0EQ4QNj2GMXMgEAAA==

Why would adding {arr2[0]} make the entire arr2 reactive?

Edit: OHHHHHHH, because it's the same text node. So it's just a side-effect:

https://svelte.dev/playground/hello-world?version=5.1.2#H4sIAAAAAAAAA3WQ0WrEIBBFf0WkDwkU0-YxNcJ-h_HBTaZdadaITpYW8d-rlqWBti_DnTtnrjKRvpoVAh1kpPjpgA7VoI_U6mvpTs6xcIMVi3fWAf7y580iWMwxlIfZG4dishOugER7_0xG8hBQIzRStS-HSZ8nBcge734WLT_viJslm51XM7-PsWnJKCrK3B4uTVUr2De8tEnoZSEG4cq77z1REnyuBRuIjJIxVrRKisQi5JNKd4gv5ibKZw5kfyf7QvKuIDXuf-jw5i-IB6etOOTVfrL5dAgfSAf0OySVvgDKhG7AjgEAAA==

Prinzhorn avatar Oct 25 '24 08:10 Prinzhorn

The problem is that both arr1 and arr2 contain a reactive value (though arr1 is additionally marked as reactive because it is defined with a rune), but the compiler compiles

arr1: [{[...arr1]}]
<br>
arr2: [{[...arr2]}]

as

$.template_effect(() => $.set_text(text, ` arr1: [${[...arr1] ?? ""}] `)); // reactive
text_1.nodeValue = ` arr2: [${[...arr2] ?? ""}]`; // static

I assume this happens because, for performance reasons, printing a bare variable (<span>{foo}</span>) is considered reactive only if the variable is defined with a rune. And a bare spreading falls in this category, but I think it shouldn't.

7nik avatar Oct 25 '24 08:10 7nik

but I think it shouldn't.

I think that's fundamentally how runes work, from what I understand. Compiler does not know arr2 is reactive. You need to mark it as such, e.g. using $derived.

Prinzhorn avatar Oct 25 '24 08:10 Prinzhorn

Yes, it doesn't know, but the spreading should be considered potentially reactive, like reading a prop {foo.bar}.

7nik avatar Oct 25 '24 09:10 7nik

Yes, it doesn't know, but the spreading should be considered potentially reactive, like reading a prop {foo.bar}.

Props are reactive by definition, as I understand things, so I don't think it's the same thing. Just because props is spreaded, doesn't mean that anything that spreads needs to get the same treatment.

In other words: Spreading props get the dynamic treatment because they are reactive, not because they have the potential to be spreaded.

webJose avatar Oct 25 '24 09:10 webJose

I meant an object's prop but not a component prop. My bad. Reading an object's property will always result in the dynamic output, even if it is obviously static.

Spreading a bare variable in the template is rare, so making it reactive will have almost no effect on performance and bundle size, so I don't see reasons not to do so.

7nik avatar Oct 25 '24 09:10 7nik

Your REPL is not in runes mode, I think this makes a big difference when discussing reactive behavior.

Prinzhorn avatar Oct 25 '24 09:10 Prinzhorn

If you mean REPL with static prop - yes, though the output is the same in the rune mode.

7nik avatar Oct 25 '24 09:10 7nik

We do this intentionally, if you want to mark a variable something as reactive, it must be $state.

trueadm avatar Oct 25 '24 09:10 trueadm

It was fixed by #14488 (v5.3.1)

7nik avatar Jan 25 '25 22:01 7nik