Repository: sveltejs/svelte (JavaScript)
Date: 2020-02-29 03:27:09+00:00
Task type: Bug Fix

Issue:
Spread Props in Component removes Reactivity
**Describe the bug**
Using the spread operator in a #each loop in variable deconstruction and use the spread props feature along side other explicit props, makes the explicit props not reactive.

**Logs**
See REPL

**To Reproduce**
https://svelte.dev/repl/3c742a2e9ab145139a64a8c344982a3e?version=3.9.2

There is a comment in the FieldSet.svelte component indicating how to remove the {...props} from being adding props to the component. Removing that will make reactivity work again.

To see it work properly, you can set this to version 3.6.7

**Expected behavior**
I expected to be able to use spread props along side explicit props and that everything is reactive. Note I was not able to reproduce unless the prop being spread was inside a #each and deconstructed. 

**Information about your Svelte project:**
See REPL

**Severity**
This is very specific and important to the project I am working on because the spread operator is allows me to only set the variables that I want to override the defaults for. If I have to explicitly set each prop then I will end up overriding defaults that I do not want to.



Issue discussion:
This also prevents the index from updating.

https://svelte.dev/repl/3d1764569a2b480ca752b7a86c5f992a?version=3.12.1
**Update:** bug still exists with `3.15.0`

```svelte
<!-- will not re-render when $activeItems changes -->
<MyComp {...data} isActive={$activeItems.has(id)}/>
```

New repro using the latest Svelte.

https://svelte.dev/repl/53a3e235612d4daebbd3df318afe187d?version=3.15.0



@arggh Very nice. I too was going to check to see if this was incidentally fixed.
Is this still an issue? Seem to be having the same problem.
replace `<Item {...item} ></Item>`
with `<svelte:component this={(v)=>{Object.assign(v.props,item); return new Item(v)}}/>`
looks like that side steps the problem
it might introduce other bugs
https://svelte.dev/repl/1da799b8838d445ca5a95019823ca220?version=3.15.0
I am seeing a similar issue, but only when I specify a key expression in the #each. Is this the same issue, or different? See here: https://svelte.dev/repl/f68a4c8fe9e546bb9f577922415e6968?version=3.19.1

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## Unreleased
 
 * In `vars` array, correctly indicate whether `module` variables are `mutated` or `reassigned` ([#3215](https://github.com/sveltejs/svelte/issues/3215))
+* Fix spread props not updating in certain situations ([#3521](https://github.com/sveltejs/svelte/issues/3521), [#4480](https://github.com/sveltejs/svelte/issues/4480))
 * In `dev` mode, check for unknown props even if the component has no writable props ([#4323](https://github.com/sveltejs/svelte/issues/4323))
 * Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463))
 * Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481))
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
@@ -224,7 +224,9 @@ export default class InlineComponentWrapper extends Wrapper {
 					const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size)
 						? renderer.dirty(Array.from(dependencies))
 						: null;
+					const unchanged = dependencies.size === 0;
 
+					let change_object;
 					if (attr.is_spread) {
 						const value = attr.expression.manipulate(block);
 						initial_props.push(value);
@@ -233,13 +235,20 @@ export default class InlineComponentWrapper extends Wrapper {
 						if (attr.expression.node.type !== 'ObjectExpression') {
 							value_object = x`@get_spread_object(${value})`;
 						}
-						changes.push(condition ? x`${condition} && ${value_object}` : value_object);
+						change_object = value_object;
 					} else {
 						const obj = x`{ ${name}: ${attr.get_value(block)} }`;
 						initial_props.push(obj);
-
-						changes.push(condition ? x`${condition} && ${obj}` : x`${levels}[${i}]`);
+						change_object = obj;
 					}
+
+					changes.push(
+						unchanged
+							? x`${levels}[${i}]`
+							: condition
+							? x`${condition} && ${change_object}`
+							: change_object
+					);
 				});
 
 				block.chunks.init.push(b`


Test patch:
diff --git a/test/runtime/samples/spread-component-2/Widget.svelte b/test/runtime/samples/spread-component-2/Widget.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-component-2/Widget.svelte
@@ -0,0 +1,13 @@
+<script>
+	export let foo;
+	export let baz;
+	export let qux;
+	export let quux;
+	export let selected;
+</script>
+
+<p>foo: {foo}</p>
+<p>baz: {baz} ({typeof baz})</p>
+<p>qux: {qux}</p>
+<p>quux: {quux}</p>
+<p>selected: {selected}</p>
diff --git a/test/runtime/samples/spread-component-2/_config.js b/test/runtime/samples/spread-component-2/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-component-2/_config.js
@@ -0,0 +1,76 @@
+export default {
+	props: {
+		list: [{
+			foo: 'lol',
+			baz: 40 + 2,
+			qux: 5,
+			quux: 'core'
+		}, {
+			foo: 'lolzz',
+			baz: 50 + 2,
+			qux: 1,
+			quux: 'quuxx'
+		}],
+	},
+
+	html: `
+		<div>
+			<p>foo: lol</p>
+			<p>baz: 42 (number)</p>
+			<p>qux: 0</p>
+			<p>quux: core</p>
+			<p>selected: true</p>
+			<p>foo: lolzz</p>
+			<p>baz: 52 (number)</p>
+			<p>qux: 0</p>
+			<p>quux: quuxx</p>
+			<p>selected: false</p>
+		</div>
+	`,
+
+	test({ assert, component, target }) {
+		component.list = [{
+			foo: 'lol',
+			baz: 40 + 3,
+			qux: 8,
+			quux: 'heart'
+		}, {
+			foo: 'lolzz',
+			baz: 50 + 3,
+			qux: 8,
+			quux: 'heartxx'
+		}];
+
+		assert.htmlEqual(target.innerHTML, `
+			<div>
+				<p>foo: lol</p>
+				<p>baz: 43 (number)</p>
+				<p>qux: 0</p>
+				<p>quux: heart</p>
+				<p>selected: true</p>
+				<p>foo: lolzz</p>
+				<p>baz: 53 (number)</p>
+				<p>qux: 0</p>
+				<p>quux: heartxx</p>
+				<p>selected: false</p>
+			</div>
+		`);
+
+		component.qux = 1;
+
+		assert.htmlEqual(target.innerHTML, `
+			<div>
+				<p>foo: lol</p>
+				<p>baz: 43 (number)</p>
+				<p>qux: 1</p>
+				<p>quux: heart</p>
+				<p>selected: false</p>
+				<p>foo: lolzz</p>
+				<p>baz: 53 (number)</p>
+				<p>qux: 1</p>
+				<p>quux: heartxx</p>
+				<p>selected: true</p>
+			</div>
+		`);
+	}
+};
diff --git a/test/runtime/samples/spread-component-2/main.svelte b/test/runtime/samples/spread-component-2/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/spread-component-2/main.svelte
@@ -0,0 +1,12 @@
+<script>
+	import Widget from './Widget.svelte';
+
+	export let list;
+	export let qux = 0;
+</script>
+
+<div>
+	{#each list as item, index (item.foo)}
+		<Widget {...item} qux={qux} selected={qux === index} />
+	{/each}
+</div>