disallow late setContext calls

pull/16197/head
Rich Harris 4 months ago
parent bc050c34b7
commit 4d05ed1139

@ -110,6 +110,12 @@ Rest element properties of `$props()` such as `%property%` are readonly
The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
```
### set_context_after_init
```
`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
```
### state_descriptors_fixed
```

@ -72,6 +72,10 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
## set_context_after_init
> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
## state_descriptors_fixed
> Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.

@ -2,6 +2,7 @@
import { DEV } from 'esm-env';
import { lifecycle_outside_component } from '../shared/errors.js';
import * as e from './errors.js';
import { source } from './reactivity/sources.js';
import {
active_effect,
@ -10,7 +11,7 @@ import {
set_active_reaction
} from './runtime.js';
import { effect, teardown } from './reactivity/effects.js';
import { legacy_mode_flag } from '../flags/index.js';
import { async_mode_flag, legacy_mode_flag } from '../flags/index.js';
/** @type {ComponentContext | null} */
export let component_context = null;
@ -65,6 +66,13 @@ export function getContext(key) {
*/
export function setContext(key, context) {
const context_map = get_or_init_context_map('setContext');
if (async_mode_flag) {
if (/** @type {ComponentContext} */ (component_context).m) {
e.set_context_after_init();
}
}
context_map.set(key, context);
return context;
}

@ -197,6 +197,21 @@ export function hydration_failed() {
}
}
/**
* `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
* @returns {never}
*/
export function set_context_after_init() {
if (DEV) {
const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/set_context_after_init`);
}
}
/**
* Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
* @returns {never}

@ -0,0 +1,11 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ target, assert, logs }) {
const button = target.querySelector('button');
flushSync(() => button?.click());
assert.ok(logs[0].startsWith('set_context_after_init'));
}
});

@ -0,0 +1,19 @@
<script>
import { setContext } from 'svelte';
let condition = $state(false);
$effect(() => {
if (condition) {
try {
setContext('potato', {});
} catch (e) {
console.log(e.message);
}
}
});
</script>
<button onclick={() => condition = !condition}>
{condition}
</button>
Loading…
Cancel
Save