fix: always allow `setContext` before first await in component (#17031)

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
pull/17029/head
Simon H 2 days ago committed by GitHub
parent 4eb432e941
commit b7f39b464a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: always allow `setContext` before first await in component

@ -13,6 +13,7 @@ export const INERT = 1 << 13;
export const DESTROYED = 1 << 14;
// Flags exclusive to effects
/** Set once an effect that should run synchronously has run */
export const EFFECT_RAN = 1 << 15;
/**
* 'Transparent' effects do not create a transition boundary.

@ -128,7 +128,11 @@ export function setContext(key, context) {
if (async_mode_flag) {
var flags = /** @type {Effect} */ (active_effect).f;
var valid = !active_reaction && (flags & BRANCH_EFFECT) !== 0 && (flags & EFFECT_RAN) === 0;
var valid =
!active_reaction &&
(flags & BRANCH_EFFECT) !== 0 &&
// pop() runs synchronously, so this indicates we're setting context after an await
!(/** @type {ComponentContext} */ (component_context).i);
if (!valid) {
e.set_context_after_init();
@ -173,6 +177,7 @@ export function getAllContexts() {
export function push(props, runes = false, fn) {
component_context = {
p: component_context,
i: false,
c: null,
e: null,
s: props,
@ -208,6 +213,8 @@ export function pop(component) {
context.x = component;
}
context.i = true;
component_context = context.p;
if (DEV) {

@ -29,7 +29,7 @@ export function handle_error(error) {
// if the error occurred while creating this subtree, we let it
// bubble up until it hits a boundary that can handle it
if ((effect.f & BOUNDARY_EFFECT) === 0) {
if (!effect.parent && error instanceof Error) {
if (DEV && !effect.parent && error instanceof Error) {
apply_adjustments(error);
}
@ -61,7 +61,7 @@ export function invoke_error_boundary(error, effect) {
effect = effect.parent;
}
if (error instanceof Error) {
if (DEV && error instanceof Error) {
apply_adjustments(error);
}

@ -1,6 +1,6 @@
import type { Store } from '#shared';
import { STATE_SYMBOL } from './constants.js';
import type { Effect, Source, Value, Reaction } from './reactivity/types.js';
import type { Effect, Source, Value } from './reactivity/types.js';
type EventCallback = (event: Event) => boolean;
export type EventCallbackMap = Record<string, EventCallback | EventCallback[]>;
@ -16,6 +16,8 @@ export type ComponentContext = {
c: null | Map<unknown, unknown>;
/** deferred effects */
e: null | Array<() => void | (() => void)>;
/** True if initialized, i.e. pop() ran */
i: boolean;
/**
* props needed for legacy mode lifecycle functions, and for `createEventDispatcher`
* @deprecated remove in 6.0

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client'],
async test() {
// else runtime_error is checked too soon
await tick();
},
runtime_error: 'set_context_after_init'
});

@ -0,0 +1,7 @@
<script>
import { setContext } from 'svelte';
await Promise.resolve('hi');
setContext('key', 'value');
</script>

@ -0,0 +1,7 @@
<script lang="ts">
import { getContext } from "svelte";
let greeting = getContext("greeting");
</script>
<p>{greeting}</p>

@ -0,0 +1,9 @@
<script lang="ts">
import { setContext } from "svelte";
import Inner from "./Inner.svelte";
setContext("greeting", "hi");
await Promise.resolve();
</script>
<Inner />

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client', 'async-server'],
ssrHtml: `<p>hi</p>`,
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, '<p>hi</p>');
}
});

@ -0,0 +1,7 @@
<script lang="ts">
import Outer from "./Outer.svelte";
await Promise.resolve();
</script>
<Outer />
Loading…
Cancel
Save