Repository: sveltejs/svelte (JavaScript)
Date: 2019-07-26 22:35:10+00:00
Task type: Bug Fix

Issue:
Spread props cause unnecessary changes
**Describe the bug**

Inline expressions are re-evaluated when setting spread props on a component.

**To Reproduce**

https://svelte.dev/repl/38ab6eb9233a4d8c97e67d576e063109?version=3.6.8

**Expected behavior**

Waggling the slider should only cause `a` to show up as having changed. With the spread version, `c` also appears to have changed.

**Severity**

Mildly annoying. Could result in incorrect behaviour (as opposed to mere redundant work)

**Additional context**

This could be fixed like so:

```diff
var nested1_changes = (changed.x || changed.a || changed.b) ? get_spread_update(nested1_spread_levels, [
  (changed.x) && ctx.x,
  (changed.a) && { a: ctx.a },
  (changed.b) && { b: ctx.b },
-  { c: [1] },
-  { d: "string" }
+  nested1_spread_levels[3],
+  nested1_spread_levels[4]
]) : {};
nested1.$set(nested1_changes);
```


Issue discussion:
Looking at that code again, I'm wondering if there's a good reason not to do this instead:

```js
if (changed.x || changed.a || changed.b) {
  nested1.$set(get_spread_update(nested1_spread_levels, [
    (changed.x) && ctx.x,
    (changed.a) && { a: ctx.a },
    (changed.b) && { b: ctx.b },
    nested1_spread_levels[3],
    nested1_spread_levels[4]
  ]));
}
```

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
@@ -185,7 +185,7 @@ export default class InlineComponentWrapper extends Wrapper {
 					add_to_set(all_dependencies, attr.dependencies);
 				});
 
-				this.node.attributes.forEach(attr => {
+				this.node.attributes.forEach((attr, i) => {
 					const { name, dependencies } = attr;
 
 					const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
@@ -201,7 +201,7 @@ export default class InlineComponentWrapper extends Wrapper {
 						const obj = `{ ${quote_name_if_necessary(name)}: ${attr.get_value(block)} }`;
 						initial_props.push(obj);
 
-						changes.push(condition ? `${condition} && ${obj}` : obj);
+						changes.push(condition ? `${condition} && ${obj}` : `${levels}[${i}]`);
 					}
 				});
 


Test patch:
diff --git a/test/runtime/samples/spread-reuse-levels/Nested.svelte b/test/runtime/samples/spread-reuse-levels/Nested.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-reuse-levels/Nested.svelte
@@ -0,0 +1,21 @@
+<script>
+	import { beforeUpdate } from 'svelte';
+
+	export let a, b, c;
+
+	let changed = {};
+	let previous = {};
+
+	beforeUpdate(() => {
+		changed.a = a !== previous.a;
+		changed.b = b !== previous.b;
+		changed.c = c !== previous.c;
+
+		previous.a = a;
+		previous.b = b;
+		previous.c = c;
+	});
+</script>
+
+<pre>{JSON.stringify({ a, b, c })}</pre>
+<pre>{JSON.stringify(changed)}</pre>
\ No newline at end of file
diff --git a/test/runtime/samples/spread-reuse-levels/_config.js b/test/runtime/samples/spread-reuse-levels/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-reuse-levels/_config.js
@@ -0,0 +1,20 @@
+export default {
+	html: `
+		<pre>{"a":1,"b":[1],"c":42}</pre>
+		<pre>{"a":false,"b":false,"c":false}</pre>
+	`,
+
+	ssrHtml: `
+		<pre>{"a":1,"b":[1],"c":42}</pre>
+		<pre>{}</pre>
+	`,
+
+	test({ assert, component, target }) {
+		component.a = 2;
+
+		assert.htmlEqual(target.innerHTML, `
+			<pre>{"a":2,"b":[1],"c":42}</pre>
+			<pre>{"a":true,"b":false,"c":false}</pre>
+		`);
+	}
+};
diff --git a/test/runtime/samples/spread-reuse-levels/main.svelte b/test/runtime/samples/spread-reuse-levels/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-reuse-levels/main.svelte
@@ -0,0 +1,8 @@
+<script>
+	import Nested from './Nested.svelte';
+
+	export let a = 1;
+	let x = {};
+</script>
+
+<Nested {...x} {a} b={[1]} c={42}/>
\ No newline at end of file