Repository: sveltejs/svelte (JavaScript)
Date: 2019-08-21 18:39:12+00:00
Task type: Bug Fix

Issue:
Incorrect context in child component inside await
[REPL](https://svelte.dev/repl?version=3.0.0-beta.28&gist=28e8889cf4022f33868d6adba34e19d0)


Issue discussion:
[The global `current_component` is reset back to `undefined` in the `App` init](https://github.com/sveltejs/svelte/blob/master/src/internal/Component.js#L112) before the [promise in `handle_promise`](https://github.com/sveltejs/svelte/blob/211d86b4f810670d6549ba196c569a0b9f5d6d6a/src/internal/await-block.js#L45) is resolved. Because of this the [`parent_component` in the `Child` init is `undefined`](https://github.com/sveltejs/svelte/blob/master/src/internal/Component.js#L57), and subsequently [the `context` is empty](https://github.com/sveltejs/svelte/blob/master/src/internal/Component.js#L77).

[The `parent_component` in the `Child` init is correctly set to `App` if you change to something synchronous like e.g. `{#await 'foo'}`](https://svelte.dev/repl?version=3.0.0-beta.28&gist=d3df3f586915f41e4f0ff7004dbefbe7), so it might be that the approach of a global `current_component` has to be changed to fix this.
@EmilTholin This issue is biting me when trying to use the `{#await ...}` block to render a `<Link>` component from your `svelte-routing`. It only happens if the route with async `<Link>` is the initial one to load. If you transition to the route via another link, it doesn't have this issue as the `Route` isn't in `init`, it is in `flush`.

I thought about raising this in `svelte-routing` (and let me know if I still should), but it is directly related to this context/`current_component` issue.
Is there any known workaround to this issue other than simply not using context at all?
I'd argue that not using `{#await}` is a more viable workaround. Context lets you do things not otherwise possible, while `{#await}` is mostly a convenience over updating certain bits of state when a promise stored in another variable resolves or rejects.
Naive fix:

```diff
diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts
index 3834ff7c..14f68d5c 100644
--- a/src/runtime/internal/await_block.ts
+++ b/src/runtime/internal/await_block.ts
@@ -1,4 +1,5 @@
 import { assign, is_promise } from './utils';
+import { get_current_component, set_current_component } from './lifecycle';
 import { check_outros, group_outros, transition_in, transition_out } from './transitions';
 import { flush } from './scheduler';
 
@@ -40,9 +41,12 @@ export function handle_promise(promise, info) {
 	}
 
 	if (is_promise(promise)) {
+		const current_component = get_current_component();
 		promise.then(value => {
+			set_current_component(current_component);
 			update(info.then, 1, info.value, value);
 		}, error => {
+			set_current_component(current_component);
 			update(info.catch, 2, info.error, error);
 		});
 
diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts
index d659fd2e..0ca3e430 100644
--- a/src/runtime/internal/lifecycle.ts
+++ b/src/runtime/internal/lifecycle.ts
@@ -6,7 +6,7 @@ export function set_current_component(component) {
 	current_component = component;
 }
 
-function get_current_component() {
+export function get_current_component() {
 	if (!current_component) throw new Error(`Function called outside component initialization`);
 	return current_component;
 }
```

This doesn't break any existing tests, and at first glance seems to fix the issue here. I have no idea whether this is a good idea.

Solution patch:
diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts
--- a/src/runtime/internal/await_block.ts
+++ b/src/runtime/internal/await_block.ts
@@ -1,6 +1,7 @@
 import { assign, is_promise } from './utils';
 import { check_outros, group_outros, transition_in, transition_out } from './transitions';
 import { flush } from './scheduler';
+import { get_current_component, set_current_component } from './lifecycle';
 
 export function handle_promise(promise, info) {
 	const token = info.token = {};
@@ -40,10 +41,15 @@ export function handle_promise(promise, info) {
 	}
 
 	if (is_promise(promise)) {
+		const current_component = get_current_component();
 		promise.then(value => {
+			set_current_component(current_component);
 			update(info.then, 1, info.value, value);
+			set_current_component(null);
 		}, error => {
+			set_current_component(current_component);
 			update(info.catch, 2, info.error, error);
+			set_current_component(null);
 		});
 
 		// if we previously had a then/catch block, destroy it
diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts
--- a/src/runtime/internal/lifecycle.ts
+++ b/src/runtime/internal/lifecycle.ts
@@ -6,7 +6,7 @@ export function set_current_component(component) {
 	current_component = component;
 }
 
-function get_current_component() {
+export function get_current_component() {
 	if (!current_component) throw new Error(`Function called outside component initialization`);
 	return current_component;
 }


Test patch:
diff --git a/test/runtime/samples/context-in-await/Child.svelte b/test/runtime/samples/context-in-await/Child.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/context-in-await/Child.svelte
@@ -0,0 +1,6 @@
+<script>
+	import { getContext } from 'svelte';
+	const num = getContext('test');
+</script>
+
+<p>Context value: {num}</p>
\ No newline at end of file
diff --git a/test/runtime/samples/context-in-await/_config.js b/test/runtime/samples/context-in-await/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/context-in-await/_config.js
@@ -0,0 +1,13 @@
+export default {
+	html: `
+		<p>...waiting</p>
+	`,
+
+	async test({ assert, component, target }) {
+		await component.promise;
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>Context value: 123</p>
+		`);
+	}
+};
\ No newline at end of file
diff --git a/test/runtime/samples/context-in-await/main.svelte b/test/runtime/samples/context-in-await/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/context-in-await/main.svelte
@@ -0,0 +1,14 @@
+<script>
+	import { setContext } from 'svelte';
+	import Child from './Child.svelte';
+
+	setContext('test', 123);
+
+	export let promise = Promise.resolve();
+</script>
+
+{#await promise}
+	<p>...waiting</p>
+{:then}
+	<Child />
+{/await}
\ No newline at end of file