Skip to content

Commit 3cd122f

Browse files
committed
fix: always allow setContext before first await in component
The previous check was flawed because EFFECT_RAN would be set by the time it is checked, since a promise in a parent component will cause a delay of the inner component being instantiated. Instead we have a new field on the component context checking if the component was already popped (if se we are indeed too late). Don't love it to have a field just for this but I don't see another way to reliably check it. Fixes #16629
1 parent c08ecba commit 3cd122f

File tree

11 files changed

+71
-4
lines changed

11 files changed

+71
-4
lines changed

.changeset/itchy-hats-study.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: always allow `setContext` before first await in component

packages/svelte/src/internal/client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const INERT = 1 << 13;
1313
export const DESTROYED = 1 << 14;
1414

1515
// Flags exclusive to effects
16+
/** Set once an effect that should run synchronously has run */
1617
export const EFFECT_RAN = 1 << 15;
1718
/**
1819
* 'Transparent' effects do not create a transition boundary.

packages/svelte/src/internal/client/context.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ export function setContext(key, context) {
128128

129129
if (async_mode_flag) {
130130
var flags = /** @type {Effect} */ (active_effect).f;
131-
var valid = !active_reaction && (flags & BRANCH_EFFECT) !== 0 && (flags & EFFECT_RAN) === 0;
131+
var valid =
132+
!active_reaction &&
133+
(flags & BRANCH_EFFECT) !== 0 &&
134+
// pop() runs synchronously, so this indicates we're setting context after an await
135+
!(/** @type {ComponentContext} */ (component_context).i);
132136

133137
if (!valid) {
134138
e.set_context_after_init();
@@ -173,6 +177,7 @@ export function getAllContexts() {
173177
export function push(props, runes = false, fn) {
174178
component_context = {
175179
p: component_context,
180+
i: false,
176181
c: null,
177182
e: null,
178183
s: props,
@@ -208,6 +213,8 @@ export function pop(component) {
208213
context.x = component;
209214
}
210215

216+
context.i = true;
217+
211218
component_context = context.p;
212219

213220
if (DEV) {

packages/svelte/src/internal/client/error-handling.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function handle_error(error) {
2929
// if the error occurred while creating this subtree, we let it
3030
// bubble up until it hits a boundary that can handle it
3131
if ((effect.f & BOUNDARY_EFFECT) === 0) {
32-
if (!effect.parent && error instanceof Error) {
32+
if (DEV && !effect.parent && error instanceof Error) {
3333
apply_adjustments(error);
3434
}
3535

@@ -61,7 +61,7 @@ export function invoke_error_boundary(error, effect) {
6161
effect = effect.parent;
6262
}
6363

64-
if (error instanceof Error) {
64+
if (DEV && error instanceof Error) {
6565
apply_adjustments(error);
6666
}
6767

packages/svelte/src/internal/client/types.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Store } from '#shared';
22
import { STATE_SYMBOL } from './constants.js';
3-
import type { Effect, Source, Value, Reaction } from './reactivity/types.js';
3+
import type { Effect, Source, Value } from './reactivity/types.js';
44

55
type EventCallback = (event: Event) => boolean;
66
export type EventCallbackMap = Record<string, EventCallback | EventCallback[]>;
@@ -16,6 +16,8 @@ export type ComponentContext = {
1616
c: null | Map<unknown, unknown>;
1717
/** deferred effects */
1818
e: null | Array<() => void | (() => void)>;
19+
/** True if initialized, i.e. pop() ran */
20+
i: boolean;
1921
/**
2022
* props — needed for legacy mode lifecycle functions, and for `createEventDispatcher`
2123
* @deprecated remove in 6.0
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
async test() {
7+
// else runtime_error is checked too soon
8+
await tick();
9+
},
10+
runtime_error: 'set_context_after_init'
11+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { setContext } from 'svelte';
3+
4+
await Promise.resolve('hi');
5+
6+
setContext('key', 'value');
7+
</script>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
import { getContext } from "svelte";
3+
4+
let greeting = getContext("greeting");
5+
</script>
6+
7+
<p>{greeting}</p>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts">
2+
import { setContext } from "svelte";
3+
import Inner from "./Inner.svelte";
4+
5+
setContext("greeting", "hi");
6+
await Promise.resolve();
7+
</script>
8+
9+
<Inner />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client', 'async-server'],
6+
ssrHtml: `<p>hi</p>`,
7+
async test({ assert, target }) {
8+
await tick();
9+
assert.htmlEqual(target.innerHTML, '<p>hi</p>');
10+
}
11+
});

0 commit comments

Comments
 (0)