diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 32348bb781..cb6b2de2cb 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -158,3 +158,23 @@ let odd = $derived(!even); ``` If side-effects are unavoidable, use [`$effect`]($effect) instead. + +### svelte_boundary_reset_onerror + +``` +A `` `reset` function cannot be called while an error is still being handled +``` + +If a [``](https://svelte.dev/docs/svelte/svelte-boundary) has an `onerror` function, it must not call the provided `reset` function synchronously since the boundary is still in a broken state. Typically, `reset()` is called later, once the error has been resolved. + +If it's possible to resolve the error inside the `onerror` callback, you must at least wait for the boundary to settle before calling `reset()`, for example using [`tick`](https://svelte.dev/docs/svelte/lifecycle-hooks#tick): + +```svelte + { + fixTheError(); + +++await tick();+++ + reset(); +}}> + + +``` diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index 948f4b9b2f..10130247e0 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -200,74 +200,6 @@ Consider the following code: To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable). -### reset_misuse - -``` -reset() was invoked and the `` template threw during flush. Calling `reset` inside the `onerror` handler while the app state is still broken can cause the fresh template to crash during its first render; the error bypassed the to avoid an infinite loop `error` → `reset` → `error` -``` - -When you call `reset()` Svelte tears down the template inside `` and renders a fresh one. If the same bad state that caused the first error in the first place is still present, that fresh mount crashes immediately. To break a potential `error → reset → error` loop, Svelte lets such render-time errors bubble past the boundary. - -Sometimes this happens because you might have called `reset` before the error was thrown (perhaps in the `onclick` handler of the button that will then trigger the error) or inside the `onerror` handler. - -`reset()` should preferably be called **after** the boundary has entered its error state. A common pattern is to call it from a "Try again" button in the fallback UI. - -If you need to call `reset` inside the `onerror` handler, ensure you fix the broken state first, *then* invoke `reset()`. - -The examples below show do's and don'ts: - -```svelte - - - - - {#if showComponent} - - {/if} - -``` - -```svelte - - { - // Fix the problematic state first - reset(); // This will cause the error to be thrown again and bypass the boundary -}}> - - -``` - -```svelte - - - - - {#snippet failed(error)} - - {/snippet} - -``` - -```svelte - - { - componentState = initialComponentState; // Fix/reset the problematic state first - reset(); // Now the regular template will show without errors -}}> - - -``` - ### select_multiple_invalid_value ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index c4e68f8fee..32c4e620e3 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -114,3 +114,21 @@ let odd = $derived(!even); ``` If side-effects are unavoidable, use [`$effect`]($effect) instead. + +## svelte_boundary_reset_onerror + +> A `` `reset` function cannot be called while an error is still being handled + +If a [``](https://svelte.dev/docs/svelte/svelte-boundary) has an `onerror` function, it must not call the provided `reset` function synchronously since the boundary is still in a broken state. Typically, `reset()` is called later, once the error has been resolved. + +If it's possible to resolve the error inside the `onerror` callback, you must at least wait for the boundary to settle before calling `reset()`, for example using [`tick`](https://svelte.dev/docs/svelte/lifecycle-hooks#tick): + +```svelte + { + fixTheError(); + +++await tick();+++ + reset(); +}}> + + +``` diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 0c75803433..6a3c79daa5 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -168,72 +168,6 @@ Consider the following code: To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable). -## reset_misuse - -> reset() was invoked and the `` template threw during flush. Calling `reset` inside the `onerror` handler while the app state is still broken can cause the fresh template to crash during its first render; the error bypassed the to avoid an infinite loop `error` → `reset` → `error` - -When you call `reset()` Svelte tears down the template inside `` and renders a fresh one. If the same bad state that caused the first error in the first place is still present, that fresh mount crashes immediately. To break a potential `error → reset → error` loop, Svelte lets such render-time errors bubble past the boundary. - -Sometimes this happens because you might have called `reset` before the error was thrown (perhaps in the `onclick` handler of the button that will then trigger the error) or inside the `onerror` handler. - -`reset()` should preferably be called **after** the boundary has entered its error state. A common pattern is to call it from a "Try again" button in the fallback UI. - -If you need to call `reset` inside the `onerror` handler, ensure you fix the broken state first, *then* invoke `reset()`. - -The examples below show do's and don'ts: - -```svelte - - - - - {#if showComponent} - - {/if} - -``` - -```svelte - - { - // Fix the problematic state first - reset(); // This will cause the error to be thrown again and bypass the boundary -}}> - - -``` - -```svelte - - - - - {#snippet failed(error)} - - {/snippet} - -``` - -```svelte - - { - componentState = initialComponentState; // Fix/reset the problematic state first - reset(); // Now the regular template will show without errors -}}> - - -``` - ## select_multiple_invalid_value > The `value` property of a `` element should be an array, but it received a non-array value. The selection will be kept as is. */ @@ -203,6 +181,17 @@ export function state_proxy_equality_mismatch(operator) { } } +/** + * A `` `reset` function only resets the boundary the first time it is called + */ +export function svelte_boundary_reset_noop() { + if (DEV) { + console.warn(`%c[svelte] svelte_boundary_reset_noop\n%cA \`\` \`reset\` function only resets the boundary the first time it is called\nhttps://svelte.dev/e/svelte_boundary_reset_noop`, bold, normal); + } else { + console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`); + } +} + /** * The `slide` transition does not work correctly for elements with `display: %value%` * @param {string} value diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js index 2a18790c61..092d7ad37d 100644 --- a/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-reset-onerror/_config.js @@ -2,17 +2,12 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - test({ assert, target, warnings }) { + test({ assert, target }) { const btn = target.querySelector('button'); btn?.click(); - assert.throws(() => { - flushSync(); - }, 'error on template render'); - - // Check that the warning is being showed to the user - assert.include(warnings[0], 'reset() was invoked'); + assert.throws(flushSync, 'svelte_boundary_reset_onerror'); // boundary content empty; only button remains assert.htmlEqual(target.innerHTML, ``);