Error boundary behavior during SSR hydration mismatches and cleanup of pending async promises
0 reputation · 06 Dec 2023, 13:40 UTC
0 reputation · 06 Dec 2023, 13:40 UTC
Determine how StencilJS’s error boundary, implemented via componentDidCatch, behaves when a server‑rendered component mismatches during hydration, and whether any in‑flight asynchronous operations are automatically cancelled when the boundary removes a component from the DOM.
StencilJS provides a manual cancellation mechanism through AbortController for async ops in componentDidLoad or event handlers, but the framework does not automatically abort requests on component unmount. Likewise, there is no built‑in timeout or cleanup hook for timers set during rendering. It is unclear if componentDidCatch fires for hydration mismatches and whether the component stack is preserved in that case. Developers must manually clear timers and abort controllers, yet a missing cleanup could lead to memory leaks or stale callbacks.
componentDidCatch trigger when a server‑rendered component fails to match the client‑side render during hydration, and does it provide a useful componentStack in that scenario?componentDidLoad) automatically aborted, or must developers explicitly use AbortController and clear timers?Yes, componentDidCatch fires for hydration mismatches — a mismatch throws during the client render pass, and that error bubbles to the nearest error boundary like any other render error. No, StencilJS does not automatically abort pending promises when the boundary removes the component. Fetches, timers, and subscriptions started in componentDidLoad keep running unless you cancel them yourself in componentWillUnload.
During hydration, the client re-renders the server-generated markup and compares it. When the output doesn't match, Stencil throws during the render/lifecycle phase, and errors raised there propagate to the closest ancestor that implements componentDidCatch(error, componentStack). The componentStack argument lists the Stencil component hierarchy at the failure point and is generally useful for identifying the offending component.
Two caveats, which I flag as needing verification against your exact Stencil version:
componentStack output for hydration-time errors; the stack may be truncated if the mismatch happens very early in hydration.When an error boundary unmounts a component, the DOM node is removed and componentWillUnload runs — but nothing reaches into your in-flight work. A fetch started in componentDidLoad will still resolve, and its continuation will run against a detached component. The same applies to setTimeout/setInterval callbacks, which can hold references to the removed instance and leak memory.
The established pattern is an AbortController stored on the instance:
private controller = new AbortController();
async componentDidLoad() {
const res = await fetch('/api/data', { signal: this.controller.signal });
// ...
}
componentWillUnload() {
this.controller.abort();
clearTimeout(this.timer);
}
Note the boundary case in your question: componentDidCatch only catches errors thrown synchronously in render/lifecycle methods or in promises awaited within them. A rejection in a fire-and-forget promise (e.g., a .then() chain never awaited by a lifecycle method) will not reach the boundary — it goes to the global unhandledrejection handler. So an error boundary is not a substitute for per-request error handling.
Date.now() so server and client output differ). Confirm componentDidCatch logs the error and inspect the componentStack content.AbortController, trigger the boundary, and watch the network panel: without abort() in componentWillUnload the request completes; with it, the request shows as cancelled.setTimeout that logs after unmount to confirm whether dangling callbacks fire — then clear it in componentWillUnload.If you can share your Stencil version, the componentStack fidelity for hydration errors is the one behavior worth confirming against that release's changelog, since it has changed over time.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.