From f4e82acc67a986c045d997d69173ffb565d8be64 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 17 Feb 2026 19:46:46 +0200 Subject: [PATCH] feat(render): add error boundary --- .../src/services/{render.ts => render.tsx} | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) rename apps/client/src/services/{render.ts => render.tsx} (72%) diff --git a/apps/client/src/services/render.ts b/apps/client/src/services/render.tsx similarity index 72% rename from apps/client/src/services/render.ts rename to apps/client/src/services/render.tsx index fb92722a68..e996412f58 100644 --- a/apps/client/src/services/render.ts +++ b/apps/client/src/services/render.tsx @@ -1,4 +1,4 @@ -import { h, VNode } from "preact"; +import { Component, h, VNode } from "preact"; import type FNote from "../entities/fnote.js"; import { renderReactWidgetAtElement } from "../widgets/react/react_utils.jsx"; @@ -6,7 +6,9 @@ import { type Bundle, executeBundleWithoutErrorHandling } from "./bundle.js"; import froca from "./froca.js"; import server from "./server.js"; -async function render(note: FNote, $el: JQuery, onError?: (e: unknown) => void) { +type ErrorHandler = (e: unknown) => void; + +async function render(note: FNote, $el: JQuery, onError?: ErrorHandler) { const relations = note.getRelations("renderNote"); const renderNoteIds = relations.map((rel) => rel.value).filter((noteId) => noteId); @@ -27,7 +29,7 @@ async function render(note: FNote, $el: JQuery, onError?: (e: unkno .then(result => { // Render JSX if (bundle.html === "") { - renderIfJsx(bundle, result, $el).catch(onError); + renderIfJsx(bundle, result, $el, onError).catch(onError); } }); } @@ -42,7 +44,7 @@ async function render(note: FNote, $el: JQuery, onError?: (e: unkno } } -async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery) { +async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery, onError?: ErrorHandler) { // Ensure the root script note is actually a JSX. const rootScriptNoteId = await froca.getNote(bundle.noteId); if (rootScriptNoteId?.mime !== "text/jsx") return; @@ -55,7 +57,23 @@ async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery VNode, {}); + const UserErrorBoundary = class UserErrorBoundary extends Component { + constructor(props: object) { + super(props); + this.state = { error: null }; + } + + componentDidCatch(error: unknown) { + onError?.(error); + this.setState({ error }); + } + + render() { + if ("error" in this.state && this.state?.error) return; + return this.props.children; + } + }; + const el = h(UserErrorBoundary, {}, h(result as () => VNode, {})); renderReactWidgetAtElement(closestComponent, el, $el[0]); }