Repository: sveltejs/svelte (JavaScript)
Date: 2019-01-09 05:08:16+00:00
Task type: Bug Fix

Issue:
Array of refs are not working
Seems original RFC and #1877 propose that we gonna able to do this to collect multiple nodes as refs:

```html
{#each items as item, i}
<a bind:this={elems[i]}>{item}</a>
{/each}

<script>
  let items = [...];
  let elems = [];
</script>
```
But for now new REPL throws an error **"i is not defined"**: https://v3.svelte.technology/repl?version=3.0.0-alpha12&gist=9e81322e43470ab531eac9c1e79dfe22





Issue discussion:

Solution patch:
diff --git a/src/compile/render-dom/wrappers/Element/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding.ts
--- a/src/compile/render-dom/wrappers/Element/Binding.ts
+++ b/src/compile/render-dom/wrappers/Element/Binding.ts
@@ -24,7 +24,8 @@ export default class BindingWrapper {
 	handler: {
 		usesContext: boolean;
 		mutation: string;
-		contextual_dependencies: Set<string>
+		contextual_dependencies: Set<string>,
+		snippet?: string
 	};
 	snippet: string;
 	initialUpdate: string;
@@ -238,9 +239,10 @@ function getEventHandler(
 
 	if (binding.node.expression.node.type === 'MemberExpression') {
 		return {
-			usesContext: false,
+			usesContext: binding.node.expression.usesContext,
 			mutation: `${snippet} = ${value};`,
-			contextual_dependencies: new Set()
+			contextual_dependencies: binding.node.expression.contextual_dependencies,
+			snippet
 		};
 	}
 
diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts
--- a/src/compile/render-dom/wrappers/Element/index.ts
+++ b/src/compile/render-dom/wrappers/Element/index.ts
@@ -493,15 +493,28 @@ export default class ElementWrapper extends Wrapper {
 
 			const { handler, object } = this_binding;
 
+			const args = [];
+			for (const arg of handler.contextual_dependencies) {
+				args.push(arg);
+				block.addVariable(arg, `ctx.${arg}`);
+			}
+
 			renderer.component.partly_hoisted.push(deindent`
-				function ${name}($$node) {
-					${handler.mutation}
+				function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
+					${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
 					$$invalidate('${object}', ${object});
 				}
 			`);
 
-			block.builders.mount.addLine(`@add_binding_callback(() => ctx.${name}(${this.var}));`);
-			block.builders.destroy.addLine(`ctx.${name}(null);`);
+			block.builders.mount.addLine(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
+			block.builders.destroy.addLine(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
+			block.builders.update.addLine(deindent`
+				if (changed.items) {
+					ctx.${name}(${['null', this.var].concat(args).join(', ')});
+					${args.map(a => `${a} = ctx.${a}`).join(', ')};
+					ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
+				}`
+			);
 		}
 	}
 


Test patch:
diff --git a/test/runtime/samples/binding-this-with-context/_config.js b/test/runtime/samples/binding-this-with-context/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/binding-this-with-context/_config.js
@@ -0,0 +1,59 @@
+export default {
+	html: `<div>foo</div><div>bar</div><div>baz</div>
+	<span>foo</span><span>bar</span><span>baz</span>
+	<ul><li><p>foo</p></li><li><p>bar</p></li><li><p>baz</p></li></ul>
+	<ul><li><hr /></li><li><hr /></li><li><hr /></li></ul>`,
+
+	test({ assert, component, target }) {
+		let elems = target.querySelectorAll('div');
+		assert.equal(component.divs.length, 3, 'three divs are registered (unkeyed array)');
+		component.divs.forEach((e, i) => {
+			assert.equal(e, elems[i], `div ${i} is correct (unkeyed array)`);
+		});
+
+		elems = target.querySelectorAll('span');
+		assert.equal(Object.keys(component.spans).length, 3, 'three spans are registered (unkeyed object)');
+		component.items.forEach((e, i) => {
+			assert.equal(component.spans[`-${e}${i}`], elems[i], `span -${e}${i} is correct (unkeyed object)`);
+		});
+
+		elems = target.querySelectorAll('p');
+		assert.equal(component.ps.length, 3, 'three ps are registered (keyed array)');
+		component.ps.forEach((e, i) => {
+			assert.equal(e, elems[i], `p ${i} is correct (keyed array)`);
+		});
+
+		elems = target.querySelectorAll('hr');
+		assert.equal(Object.keys(component.hrs).length, 3, 'three hrs are registered (keyed object)');
+		component.items.forEach((e, i) => {
+			assert.equal(component.hrs[e], elems[i], `hr ${e} is correct (keyed object)`);
+		});
+
+		component.items = ['foo', 'baz'];
+		assert.equal(component.divs.length, 3, 'the divs array is still 3 long');
+		assert.equal(component.divs[2], null, 'the last div is unregistered');
+		assert.equal(component.ps[2], null, 'the last p is unregistered');
+		assert.equal(component.spans['-bar1'], null, 'the bar span is unregisterd');
+		assert.equal(component.hrs.bar, null, 'the bar hr is unregisterd');
+
+		elems = target.querySelectorAll('div');
+		component.divs.forEach((e, i) => {
+			assert.equal(e, elems[i], `div ${i} is still correct`);
+		});
+
+		elems = target.querySelectorAll('span');
+		component.items.forEach((e, i) => {
+			assert.equal(component.spans[`-${e}${i}`], elems[i], `span -${e}${i} is still correct`);
+		});
+
+		elems = target.querySelectorAll('p');
+		component.ps.forEach((e, i) => {
+			assert.equal(e, elems[i], `p ${i} is still correct`);
+		});
+
+		elems = target.querySelectorAll('hr');
+		component.items.forEach((e, i) => {
+			assert.equal(component.hrs[e], elems[i], `hr ${e} is still correct`);
+		});
+	}
+};
diff --git a/test/runtime/samples/binding-this-with-context/main.html b/test/runtime/samples/binding-this-with-context/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/binding-this-with-context/main.html
@@ -0,0 +1,28 @@
+<script>
+  export let items = ['foo', 'bar', 'baz'];
+	export let divs = [];
+	export let spans = {};
+	export let ps = [];
+	export let hrs = {};
+	const prefix = '-';
+</script>
+
+{#each items as item, j}
+	<div bind:this={divs[j]}>{item}</div>
+{/each}
+
+{#each Object.entries(items) as [ key, val ] }
+	<span bind:this="{spans[prefix + val + key]}">{val}</span>
+{/each}
+
+<ul>
+	{#each items as thing, j (thing)}
+		<li><p bind:this="{ps[j]}">{thing}</p></li>
+	{/each}
+</ul>
+
+<ul>
+	{#each items as sure, j (sure)}
+		<li><hr bind:this="{hrs[sure]}" /></li>
+	{/each}
+</ul>
\ No newline at end of file