Skip to content

Commit 28cc82a

Browse files
committed
Add a warning when the misuse of reset in an error:boundary causes an error to be thrown when flushing the boundary content
1 parent 5466086 commit 28cc82a

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

documentation/docs/98-reference/.generated/client-warnings.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,74 @@ Consider the following code:
200200
201201
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
202202
203+
### reset_misuse
204+
205+
```
206+
reset() was invoked and the `<svelte:boundary>` 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 <svelte:boundary> to avoid an infinite loop `error``reset``error`
207+
```
208+
209+
When you call `reset()` Svelte tears down the template inside `<svelte:boundary>` 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.
210+
211+
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.
212+
213+
`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.
214+
215+
If you need to call `reset` inside the `onerror` handler, ensure you fix the broken state first, *then* invoke `reset()`.
216+
217+
The examples below show do's and don'ts:
218+
219+
```svelte
220+
<!-- ❌ Don't call reset before errors occur -->
221+
<button onclick={() => {
222+
showComponent = true;
223+
if (reset) reset(); // Called before knowing if error will occur
224+
}}>
225+
Update
226+
</button>
227+
228+
<svelte:boundary>
229+
{#if showComponent}
230+
<!-- ... -->
231+
{/if}
232+
</svelte:boundary>
233+
```
234+
235+
```svelte
236+
<!-- ❌ Don't call reset without fixing the problematic state -->
237+
<svelte:boundary onerror={() => {
238+
// Fix the problematic state first
239+
reset(); // This will cause the error to be thrown again and bypass the boundary
240+
}}>
241+
<!-- ... -->
242+
</svelte:boundary>
243+
```
244+
245+
```svelte
246+
<!-- ✅ Call reset from error UI -->
247+
<svelte:boundary>
248+
<!-- ... -->
249+
250+
{#snippet failed(error)}
251+
<button onclick={() => {
252+
// Fix the problematic state first
253+
selectedItem = null;
254+
userInput = '';
255+
reset(); // Now safe to retry
256+
}}>Try Again</button>
257+
{/snippet}
258+
</svelte:boundary>
259+
```
260+
261+
```svelte
262+
<!-- ✅ Or fix the problematic state first and call reset in the onerror for immediate recovery -->
263+
<svelte:boundary onerror={() => {
264+
componentState = initialComponentState; // Fix/reset the problematic state first
265+
reset(); // Now the regular template will show without errors
266+
}}>
267+
<!-- ... -->
268+
</svelte:boundary>
269+
```
270+
203271
### select_multiple_invalid_value
204272
205273
```

packages/svelte/messages/client-warnings/warnings.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,72 @@ Consider the following code:
168168
169169
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
170170
171+
## reset_misuse
172+
173+
> reset() was invoked and the `<svelte:boundary>` 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 <svelte:boundary> to avoid an infinite loop `error``reset``error`
174+
175+
When you call `reset()` Svelte tears down the template inside `<svelte:boundary>` 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.
176+
177+
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.
178+
179+
`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.
180+
181+
If you need to call `reset` inside the `onerror` handler, ensure you fix the broken state first, *then* invoke `reset()`.
182+
183+
The examples below show do's and don'ts:
184+
185+
```svelte
186+
<!-- ❌ Don't call reset before errors occur -->
187+
<button onclick={() => {
188+
showComponent = true;
189+
if (reset) reset(); // Called before knowing if error will occur
190+
}}>
191+
Update
192+
</button>
193+
194+
<svelte:boundary>
195+
{#if showComponent}
196+
<!-- ... -->
197+
{/if}
198+
</svelte:boundary>
199+
```
200+
201+
```svelte
202+
<!-- ❌ Don't call reset without fixing the problematic state -->
203+
<svelte:boundary onerror={() => {
204+
// Fix the problematic state first
205+
reset(); // This will cause the error to be thrown again and bypass the boundary
206+
}}>
207+
<!-- ... -->
208+
</svelte:boundary>
209+
```
210+
211+
```svelte
212+
<!-- ✅ Call reset from error UI -->
213+
<svelte:boundary>
214+
<!-- ... -->
215+
216+
{#snippet failed(error)}
217+
<button onclick={() => {
218+
// Fix the problematic state first
219+
selectedItem = null;
220+
userInput = '';
221+
reset(); // Now safe to retry
222+
}}>Try Again</button>
223+
{/snippet}
224+
</svelte:boundary>
225+
```
226+
227+
```svelte
228+
<!-- ✅ Or fix the problematic state first and call reset in the onerror for immediate recovery -->
229+
<svelte:boundary onerror={() => {
230+
componentState = initialComponentState; // Fix/reset the problematic state first
231+
reset(); // Now the regular template will show without errors
232+
}}>
233+
<!-- ... -->
234+
</svelte:boundary>
235+
```
236+
171237
## select_multiple_invalid_value
172238
173239
> The `value` property of a `<select multiple>` element should be an array, but it received a non-array value. The selection will be kept as is.

packages/svelte/src/internal/client/dom/blocks/boundary.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
set_hydrate_node
2020
} from '../hydration.js';
2121
import { queue_micro_task } from '../task.js';
22+
import * as w from '../../warnings.js';
2223

2324
/**
2425
* @param {Effect} boundary
@@ -73,12 +74,20 @@ export function boundary(node, props, boundary_fn) {
7374
throw error;
7475
}
7576

77+
// boundary entered error mode
78+
7679
var reset = () => {
7780
pause_effect(boundary_effect);
7881

7982
with_boundary(boundary, () => {
8083
is_creating_fallback = false;
81-
boundary_effect = branch(() => boundary_fn(anchor));
84+
try {
85+
boundary_effect = branch(() => boundary_fn(anchor));
86+
} catch (error) {
87+
// If the new subtree immediately throws during mount, warn the dev.
88+
w.reset_misuse();
89+
throw error;
90+
}
8291
});
8392
};
8493

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ export function ownership_invalid_mutation(name, location, prop, parent) {
158158
}
159159
}
160160

161+
/**
162+
* reset() was invoked and the `<svelte:boundary>` 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 <svelte:boundary> to avoid an infinite loop `error` → `reset` → `error`
163+
*/
164+
export function reset_misuse() {
165+
if (DEV) {
166+
console.warn(`%c[svelte] reset_misuse\n%creset() was invoked and the \`<svelte:boundary>\` 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 <svelte:boundary> to avoid an infinite loop \`error\` → \`reset\` → \`error\`\nhttps://svelte.dev/e/reset_misuse`, bold, normal);
167+
} else {
168+
console.warn(`https://svelte.dev/e/reset_misuse`);
169+
}
170+
}
171+
161172
/**
162173
* The `value` property of a `<select multiple>` element should be an array, but it received a non-array value. The selection will be kept as is.
163174
*/

0 commit comments

Comments
 (0)