From 30cea1f9a0cbf41f466083f9761a8f4d544d700a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 27 Jan 2026 19:16:59 -0500 Subject: [PATCH] fix: run boundary `onerror` callbacks in a microtask, in case they result in the boundary's destruction (#17561) * fix: mark parent effect as dirty on error boundary initialization This change ensures that if an error occurs while a parent effect is still initializing, the parent effect is marked as dirty and scheduled to re-run. This addresses scenarios where state updates in the onerror handler do not trigger a re-evaluation of the condition, improving error handling in Svelte components. * chore: add changeset for #17553 * fix: run boundary callbacks in a microtask, in case they result in the boundary's destruction --------- Co-authored-by: frozenflux2 --- .changeset/fix-if-block-boundary-onerror.md | 5 ++++ .../internal/client/dom/blocks/boundary.js | 28 ++++++++----------- .../samples/error-boundary-24/Child.svelte | 5 ++++ .../samples/error-boundary-24/_config.js | 13 +++++++++ .../samples/error-boundary-24/main.svelte | 19 +++++++++++++ .../samples/error-boundary-25/Child.svelte | 8 ++++++ .../samples/error-boundary-25/_config.js | 13 +++++++++ .../samples/error-boundary-25/main.svelte | 19 +++++++++++++ 8 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 .changeset/fix-if-block-boundary-onerror.md create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-24/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-24/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-24/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-25/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-25/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/error-boundary-25/main.svelte diff --git a/.changeset/fix-if-block-boundary-onerror.md b/.changeset/fix-if-block-boundary-onerror.md new file mode 100644 index 0000000000..608dce6814 --- /dev/null +++ b/.changeset/fix-if-block-boundary-onerror.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: run boundary `onerror` callbacks in a microtask, in case they result in the boundary's destruction diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 90a695551a..ef5f0e116d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,5 +1,6 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ import { + BLOCK_EFFECT, BOUNDARY_EFFECT, COMMENT_NODE, DIRTY, @@ -449,21 +450,16 @@ export class Boundary { } }; - var previous_reaction = active_reaction; - - try { - set_active_reaction(null); - calling_on_error = true; - onerror?.(error, reset); - calling_on_error = false; - } catch (error) { - invoke_error_boundary(error, this.#effect && this.#effect.parent); - } finally { - set_active_reaction(previous_reaction); - } + queue_micro_task(() => { + try { + calling_on_error = true; + onerror?.(error, reset); + calling_on_error = false; + } catch (error) { + invoke_error_boundary(error, this.#effect && this.#effect.parent); + } - if (failed) { - queue_micro_task(() => { + if (failed) { this.#failed_effect = this.#run(() => { Batch.ensure(); this.#is_creating_fallback = true; @@ -483,8 +479,8 @@ export class Boundary { this.#is_creating_fallback = false; } }); - }); - } + } + }); } } diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-24/Child.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/Child.svelte new file mode 100644 index 0000000000..cd25ece1b5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/Child.svelte @@ -0,0 +1,5 @@ + + +

Child content

diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-24/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/_config.js new file mode 100644 index 0000000000..b5b5f296d8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + test({ assert, target }) { + flushSync(); + + // When exception is set by onerror, the {#if !exception} block should hide + // and only the {#if exception} block should be visible + assert.htmlEqual(target.innerHTML, '

caught error: child error

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-24/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/main.svelte new file mode 100644 index 0000000000..5116580003 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-24/main.svelte @@ -0,0 +1,19 @@ + + +{#if !exception} +

condition is {String(!exception)}

+ + + +{/if} + +{#if exception} +

caught error: {exception.message}

+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-25/Child.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/Child.svelte new file mode 100644 index 0000000000..3d5770fc61 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/Child.svelte @@ -0,0 +1,8 @@ + + +

+ boom +

diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-25/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/_config.js new file mode 100644 index 0000000000..b5b5f296d8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + test({ assert, target }) { + flushSync(); + + // When exception is set by onerror, the {#if !exception} block should hide + // and only the {#if exception} block should be visible + assert.htmlEqual(target.innerHTML, '

caught error: child error

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-25/main.svelte b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/main.svelte new file mode 100644 index 0000000000..243d10cf3a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-25/main.svelte @@ -0,0 +1,19 @@ + + +{#if !exception} +

condition is {String(!exception)}

+ + + +{/if} + +{#if exception} +

caught error: {exception}

+{/if}