Repository: sveltejs/svelte (JavaScript)
Date: 2020-01-06 13:15:56+00:00
Task type: Bug Fix

Issue:
Destructuring a store object retuns the whole store if the property does not exist
**Describe the bug**
When destructuring a store, the whole store object is returned if the property does not exist:

`$: ({shouldBeUndefined} = $store);`

It works if used with an existing property, i.e.:

`$: ({existingProperty, shouldBeUndefined} = $store);`

**To Reproduce**
You can find the REPL [here](https://svelte.dev/repl/52333798bbea4d1b8bb5e249be1cca18?version=3.16.7)

**Information about your Svelte project:**

- Your browser and the version: Firefox 71
- Your operating system: Windows 10
- Svelte version : 3.16.7
- Whether your project uses Webpack or Rollup : Rollup

**Severity**
Annoying

Thanks!


Issue discussion:
Interesting. The problem is that [here](https://github.com/sveltejs/svelte/blob/cd21acfb3cae574b81f2f417331993374222a9de/src/runtime/internal/Component.ts#L130) where we're defining the `$$invalidate` function as `(i, ret, value = ret) => { ... }`, the third argument will default to being the same as the second argument - even if it was explicitly passed `undefined`, because that's how that works in javascript. I'm not sure what the most elegant way to fix this is. It would be nice to maintain the two-argument version of `$$invalidate`, because a lot of the time that's all we need. I guess we could look at `arguments.length`, but that doesn't feel great to me.
@Conduitry in which conditions `value` is passed to `$$invalidate`? I can't find any time were the compiler renders `$$invalidate` with a third argument: https://github.com/sveltejs/svelte/blob/a8b306f0a18775b31b57333d938090eb3934eb29/src/compiler/compile/render_dom/Renderer.ts#L151-L198

But anyway, maybe we could check if `value` is defined or not, do some stuff and then assign him `ret`'s value?
The original REPL that reproduces this error is one where `$$invalidate()` gets called with three arguments - `$$invalidate(1, { shouldBeUndefined } = $store, shouldBeUndefined);`. Checking whether `value` is undefined won't help, because (like it is now where it defaults to `ret`) that doesn't distinguish between it not being passed at all and it being passed as `undefined`.
I see 3 ways to solve this:

**1.** Using `arguments.length` as @Conduitry said.

**2.** Creating two `$$invalidate` (for example: `$$invalidateA`, `$$invalidateB`). One with two arguments (`i, ret`) and one with three (`i, ret, value`).

**3.** Passing a single object though `$$invalidate` which contain three properties: `i, ret, value`. Then we can check if `value` is given in the object with something like:
```js
!!Object.keys({ i: 1, ret: { value: 'test' }, value: undefined }).find(e => e === 'value') // true

!!Object.keys({ i: 1, ret: { value: 'test' } }).find(e => e === 'value') // false
``` 
I haven't tried this yet, but what seems like it might be a good solution is to make `value` be the second argument of the `$$invalidate()` function, and to have `ret` be the third and default it to `value`.
I guess you mean `(i, value, ret = value) => { ... }`, then I also guess it should be changed in the compiler?
But even, how could this solve the issue?
I'm sorry if I ask too much questions, I'm just trying to understand the project so I can try to contribute..
@AnatoleLucet The way the method is currently set up, if value is undefined then it is made to equal ret, which means if value was explicitly undefined, there is no way of knowing (since it is then set to ret). If changed around as @Conduitry proposed, value could be explicitly undefined. I also don't think it would require any rewriting for the compiler, since it would work roughly the same as it did previously, with the exception of being able to handle explicitly undefined values.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 * Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449))
 * Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047))
+* Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170))
 * Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212))
 
 ## 3.16.7
diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -127,7 +127,8 @@ export function init(component, options, instance, create_fragment, not_equal, p
 	let ready = false;
 
 	$$.ctx = instance
-		? instance(component, prop_values, (i, ret, value = ret) => {
+		? instance(component, prop_values, (i, ret, ...rest) => {
+			const value = rest.length ? rest[0] : ret;
 			if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
 				if ($$.bound[i]) $$.bound[i](value);
 				if (ready) make_dirty(component, i);


Test patch:
diff --git a/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js b/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js
@@ -0,0 +1,6 @@
+export default {
+	html: `
+		<p>undefined</p>
+		<p>undefined</p>
+	`
+};
diff --git a/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte b/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte
@@ -0,0 +1,9 @@
+<script>
+	import { writable } from 'svelte/store';
+	const store = writable([]);
+	$: ({ foo1 } = $store);
+	$: [foo2] = $store;
+</script>
+
+<p>{foo1}</p>
+<p>{foo2}</p>