Repository: sveltejs/svelte (JavaScript)
Date: 2021-01-16 19:03:48+00:00
Task type: Bug Fix

Issue:
Binding to store value properties causes addition change per binding
**Describe the bug**
When binding to a property of a store value, the value is updated / changed (subscribe called) for each bind statement, instead of just once for the initialization.

**To Reproduce**
REPL: https://svelte.dev/repl/83c80ca45be54486ae7fd15827660280?version=3.29.0

![image](https://user-images.githubusercontent.com/177476/96744103-b05d7580-1392-11eb-9a00-73d7376fd228.png)

In the REPL, I would expect "pagination changed!" to be logged once when initialized, but it is being logged 3 times (2 additional times due to each `bind:`).

After the initial 3, only a single subscribe is called when changing anything on the store, as expected.

**Expected behavior**
I would expect a single call to `subscribe` on initialization, instead of ```1 + (number of `bind:` properties)```

**Information about your Svelte project:**

- Svelte version: 3.29.0

**Severity**
In my application, this is causing all my HTTP requests to fire multiple times on init due to the changing store value, which makes this a rather significant issue for my project.


Issue discussion:
This actually seems to be a pretty annoying bug. It doesn't apply only to stores. Binding all complex structures (objects, arrays) fires another extra update. Here's a link to my simplified REPL.

https://svelte.dev/repl/2bbb8d98fc8a49398d269dff0eb9843b?version=3.29.7

@techniq If you may, please update the bug title to something more straightforward, so as it gets more attention.

Solution patch:
diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts
--- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts
+++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts
@@ -347,8 +347,8 @@ export default class InlineComponentWrapper extends Wrapper {
 			}
 
 			const params = [x`#value`];
+			const args = [x`#value`];
 			if (contextual_dependencies.length > 0) {
-				const args = [];
 
 				contextual_dependencies.forEach(name => {
 					params.push({
@@ -361,25 +361,30 @@ export default class InlineComponentWrapper extends Wrapper {
 				});
 
 
-				block.chunks.init.push(b`
-					function ${id}(#value) {
-						${callee}.call(null, #value, ${args});
-					}
-				`);
-
 				block.maintain_context = true; // TODO put this somewhere more logical
-			} else {
-				block.chunks.init.push(b`
-					function ${id}(#value) {
-						${callee}.call(null, #value);
+			}
+
+			block.chunks.init.push(b`
+				function ${id}(#value) {
+					${callee}(${args});
+				}
+			`);
+
+			let invalidate_binding = b`
+				${lhs} = #value;
+				${renderer.invalidate(dependencies[0])};
+			`;
+			if (binding.expression.node.type === 'MemberExpression') {
+				invalidate_binding = b`
+					if ($$self.$$.not_equal(${lhs}, #value)) {
+						${invalidate_binding}
 					}
-				`);
+				`;
 			}
 
 			const body = b`
 				function ${id}(${params}) {
-					${lhs} = #value;
-					${renderer.invalidate(dependencies[0])};
+					${invalidate_binding}
 				}
 			`;
 


Test patch:
diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte b/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte
@@ -0,0 +1,6 @@
+<script>
+	export let value;
+	export let value2;
+</script>
+
+{value}{value2}
diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js b/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js
@@ -0,0 +1,5 @@
+export default {
+	async test({ assert, component }) {
+		assert.equal(component.object_updates, component.primitive_updates);
+	}
+};
diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte b/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte
@@ -0,0 +1,13 @@
+<script>
+	import Component from './Component.svelte';
+
+	export let primitive_updates = 0;
+	export let object_updates = 0;
+
+	const obj = { foo: '' };
+	let foo = 'bar';
+	$: if (obj) object_updates++;
+	$: if (foo) primitive_updates++;
+</script>
+
+<Component bind:value={obj.foo} bind:value2={foo} />