Skip to content
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

Binding in slotted compoment triggers reactive statements twice #5720

Closed
zqianem opened this issue Nov 25, 2020 · 12 comments
Closed

Binding in slotted compoment triggers reactive statements twice #5720

zqianem opened this issue Nov 25, 2020 · 12 comments
Milestone

Comments

@zqianem
Copy link
Contributor

zqianem commented Nov 25, 2020

Describe the bug
When a variable is bound to a slotted component, changing that variable within the slotted component causes any reactive statements inside the slotted component to trigger twice.

To Reproduce
https://svelte.dev/repl/df6fe28c60e84c26a141debf305ed114?version=3.30.0

Expected behavior
Pressing the "Update x" button should cause only one console.log call, which is what happens when the <Wrapper> is removed.

Severity
Low — can work around this by using a store instead of binding.

@non25
Copy link

non25 commented Nov 25, 2020

This is known complex types binding behavior.
Nothing to do with slotting.
Change to primitive like number and it will work once.

Workarounds

Binding arrays: use immutable in child component like this.
https://svelte.dev/repl/e4bd72b1ad5c4a4da4a76a32241ba180

Binding objects fields:

$: doSomething(obj.foo);

let prevFoo;
$: if (obj.foo !== prevFoo ) {
  prevFoo = obj.foo;
  doSomething(obj.foo);
}

or:

$: foo = obj.foo;
$: doSomething(foo);

@zqianem
Copy link
Contributor Author

zqianem commented Nov 25, 2020

This is known complex types binding behavior.
Nothing to do with slotting.
Change to primitive like number and it will work once.

I don't think this is true in this case. In the original repl, not using the slot by removing <Wrapper> changes the behavior from two console.log calls per "Update x" button press to one. Changing x to a primitive actually causes no console.log calls. (I suspect Svelte checks if the value has changed if it is a primitive, since the assignment in the original repl is just x = x.)

@al6x
Copy link

al6x commented Jun 17, 2021

This Stack Overflow Question Why Svelte component updated variable, where in reality it didn't changed?
could be related.

@blairn
Copy link

blairn commented Nov 16, 2021

It is VERY much a slot thing, replicated in https://svelte.dev/repl/2634d07aefa9465ea4ea7ec4bf810e8c?version=3.44.1

remove the slot and the behaviour vanishes.
remove the bind and the behaviour vanishes.
change the variable to a primitive and the behaviour vanishes.

@Theo-Steiner
Copy link
Contributor

Theo-Steiner commented Nov 16, 2021

This is known complex types binding behavior.
Nothing to do with slotting.
Change to primitive like number and it will work once.

I don't think this is true in this case. In the original repl, not using the slot by removing <Wrapper> changes the behavior from two console.log calls per "Update x" button press to one. Changing x to a primitive actually causes no console.log calls. (I suspect Svelte checks if the value has changed if it is a primitive, since the assignment in the original repl is just x = x.)

You are seeing this behavior because reactivity for objects (arrays) works fundamentally different for objects than for primitive types.
See this REPL for a demonstration of this.
Therefore as @blairn and @non25 pointed out, this seems to be indeed a problem with the reactivity of non-primitive types inside components that are embedded into a slot.

@iacore
Copy link

iacore commented Jan 5, 2022

@zqianem This issue apparently fixed now. Seems like properties are cached and compared to previous value using ===. The example in the first comment of this issue only outputs 0 once (or never) inside REPL.

@zqianem
Copy link
Contributor Author

zqianem commented Jan 6, 2022

@zqianem This issue apparently fixed now. Seems like properties are cached and compared to previous value using ===. The example in the first comment of this issue only outputs 0 once (or never) inside REPL.

Testing the original REPL with the latest of version of Svelte and the issue remains:
https://svelte.dev/repl/df6fe28c60e84c26a141debf305ed114?version=3.44.3

I'm not seeing 0 as an output anywhere (nor should it be?)

@iacore
Copy link

iacore commented Jan 6, 2022

@zqianem Sorry, I changed x to a number, and it worked. It seems like only arrays and objects trigger double update, while numbers like 0 or NaN works correctly.

Here's with let x = x + 1:
https://svelte.dev/repl/ae1d64a4ac874b619671937f86950b82?version=3.44.3

@iacore
Copy link

iacore commented Jan 6, 2022

Related issuue: #3617

@iacore
Copy link

iacore commented Jan 6, 2022

The current architecture of Svelte propagates redraw through components without checking if has been updated. So if you have two path in the dependency graph.

main -> wrapper -> slot
and
main -> slot

One way to solve this is to send the origin component id and component event id together, like main-frame-0 for one value update.

@zqianem
Copy link
Contributor Author

zqianem commented Dec 13, 2022

This issue seems to have been partially fixed by #7981.

Now, the initial page load in the reproduction only prints "{}" once instead of twice. However, button presses still cause two prints. Compare the following:

Original: https://svelte.dev/repl/df6fe28c60e84c26a141debf305ed114?version=3.30.0
New: https://svelte.dev/repl/df6fe28c60e84c26a141debf305ed114?version=3.54.0

@dummdidumm
Copy link
Member

This will be fixed in Svelte 5 - but also, slots will be deprecated in favor of the more powerful and easier to use snippets

@dummdidumm dummdidumm added this to the 5.x milestone Nov 15, 2023
@benmccann benmccann modified the milestones: 5.x, 5.0 Nov 17, 2023
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