Repository: sveltejs/svelte (JavaScript)
Date: 2020-02-09 13:50:18+00:00
Task type: Bug Fix

Issue:
Element event directives reactivity
I'm in a context similar to the one below: 

Some DOM element (here a `button`), which when clicked should call a callback function (`test`), which may change depending on a certain state (the variable `bool`)

```javascript
let bool = false
	
let foo = () => console.log("foo")
let bar = () => console.log("bar")

$: test = bool ? foo : bar;
```

When the variable `bool` change, the callback is only updated when writing the directive like this:

```html
<button on:click={() => test()} />
```
But if written that way, the callback will keep its first value even if the state change:

```html
<button on:click={test} />
```
I would expect the two declarations to behave in the same way.

Replicate:
https://svelte.dev/repl/ca0fbda83f654f5a8829282a59f511f1?version=3.18.1


Issue discussion:
#3836 and a few followup PRs added support for dynamic event handler when the compiler can see that the event handler is getting reassigned (e.g,, if the `swap()` function instead directly assigned either `foo` or `bar` to `test`, this would work), but apparently it's not taking this reactive declaration into account.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 * Permit reserved keywords as destructuring keys in `{#each}` ([#4372](https://github.com/sveltejs/svelte/issues/4372))
 * Disallow reserved keywords in `{expressions}` ([#4372](https://github.com/sveltejs/svelte/issues/4372))
 * Fix code generation error with precedence of arrow functions ([#4384](https://github.com/sveltejs/svelte/issues/4384))
+* Fix event handlers that are dynamic via reactive declarations or stores ([#4388](https://github.com/sveltejs/svelte/issues/4388))
 * Fix invalidation in expressions like `++foo.bar` ([#4393](https://github.com/sveltejs/svelte/issues/4393))
 
 ## 3.18.1
diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts
--- a/src/compiler/compile/nodes/EventHandler.ts
+++ b/src/compiler/compile/nodes/EventHandler.ts
@@ -53,13 +53,6 @@ export default class EventHandler extends Node {
 		}
 		const node = this.expression.node;
 
-		if (node.type === 'Identifier') {
-			return (
-				this.component.node_for_declaration.get(node.name) &&
-				this.component.var_lookup.get(node.name).reassigned
-			);
-		}
-
 		if (/FunctionExpression/.test(node.type)) {
 			return false;
 		}


Test patch:
diff --git a/test/runtime/samples/event-handler-dynamic-2/_config.js b/test/runtime/samples/event-handler-dynamic-2/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-dynamic-2/_config.js
@@ -0,0 +1,33 @@
+export default {
+	html: `
+		<button>toggle</button>
+		<p>0</p>
+		<button>handler_a</button>
+		<button>handler_b</button>
+	`,
+
+	async test({ assert, target, window }) {
+		const [toggle, handler_a, handler_b] = target.querySelectorAll('button');
+		const p = target.querySelector('p');
+
+		const event = new window.MouseEvent('click');
+
+		await handler_a.dispatchEvent(event);
+		assert.equal(p.innerHTML, '1');
+
+		await toggle.dispatchEvent(event);
+
+		await handler_a.dispatchEvent(event);
+		assert.equal(p.innerHTML, '2');
+
+		await toggle.dispatchEvent(event);
+
+		await handler_b.dispatchEvent(event);
+		assert.equal(p.innerHTML, '1');
+
+		await toggle.dispatchEvent(event);
+
+		await handler_b.dispatchEvent(event);
+		assert.equal(p.innerHTML, '2');
+	},
+};
diff --git a/test/runtime/samples/event-handler-dynamic-2/main.svelte b/test/runtime/samples/event-handler-dynamic-2/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/event-handler-dynamic-2/main.svelte
@@ -0,0 +1,20 @@
+<script>
+	import { writable } from 'svelte/store';
+
+	let number = 0;
+	const handler_1 = () => number = 1;
+	const handler_2 = () => number = 2;
+
+	let flag = true;
+
+	$: handler_a = flag ? handler_1 : handler_2;
+	const handler_b = writable();
+	$: handler_b.set(flag ? handler_1 : handler_2);
+</script>
+
+<button on:click={() => flag = !flag}>toggle</button>
+
+<p>{number}</p>
+
+<button on:click={handler_a}>handler_a</button>
+<button on:click={$handler_b}>handler_b</button>