From e9a2b70728338f6a6048ca47ac1e4cb10c68f3b4 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 16 Aug 2026 11:12:15 +0200 Subject: [PATCH] Web: Fehlergrenzen - weisse Seiten zeigen jetzt, was schiefging Im ganzen Frontend gab es keine einzige Error Boundary. In React 18 haengt eine Ausnahme beim Rendern damit den KOMPLETTEN Baum aus: die Seite wird schlagartig weiss und nur ein Neuladen hilft. Genau das gemeldete Bild - "keine Karten", "gar nichts", "muss F5 druecken". Schlimmer noch: man sah nicht, WAS passiert ist, und konnte es folglich auch nicht melden. Zwei Ebenen: eine aeussere in main.jsx als letzte Instanz (faengt auch Fehler in den Providern) und eine je Seite in Protected, damit bei einem Absturz wenigstens die Navigation bedienbar bleibt. Die innere haengt am Pfad, sonst haenge man nach einem Fehler dauerhaft in der Fehleranzeige fest. Angezeigt wird die Meldung selbst, nicht nur ein "Ups", plus die technischen Angaben zum Aufklappen. Damit ist der naechste Vorfall in einer Minute eingeordnet statt in einer Stunde. Das erklaert die Haeufigkeit, die meine vorherige Erklaerung (abgelaufene Anmeldung) nicht hergab: ein Token gilt sieben Tage, nicht zwanzig Minuten. Co-Authored-By: Claude Opus 5 --- web/src/App.jsx | 11 ++++- web/src/components/ErrorBoundary.jsx | 74 ++++++++++++++++++++++++++++ web/src/main.jsx | 5 ++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 web/src/components/ErrorBoundary.jsx diff --git a/web/src/App.jsx b/web/src/App.jsx index 23d3118..6b99398 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -1,8 +1,9 @@ import { useEffect, useState } from "react"; -import { NavLink, Navigate, Route, Routes, useNavigate } from "react-router-dom"; +import { NavLink, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { api } from "./api"; import { useAuth } from "./auth"; import Icon from "./components/Icon"; +import ErrorBoundary from "./components/ErrorBoundary"; import BrandMark from "./components/BrandMark"; import Login from "./pages/Login"; import Dashboard from "./pages/Dashboard"; @@ -138,6 +139,7 @@ function StartFehler({ text, erneut }) { function Protected({ children, adminOnly = false, wide = false }) { const { user, isAdmin, loading, startFehler, erneutVersuchen } = useAuth(); + const { pathname: pfad } = useLocation(); if (loading) return
Lädt…
; if (startFehler) return ; if (!user) return ; @@ -147,7 +149,12 @@ function Protected({ children, adminOnly = false, wide = false }) {
{/* `wide` hebt die Lesebreite auf – nur fürs Dashboard-Raster sinnvoll. */} -
{children}
+
+ {/* Je Seite eine eigene Grenze: stuerzt eine ab, bleibt wenigstens + die Navigation bedienbar. `key` setzt sie beim Seitenwechsel + zurueck, sonst haenge man nach einem Fehler dauerhaft fest. */} + {children} +
); diff --git a/web/src/components/ErrorBoundary.jsx b/web/src/components/ErrorBoundary.jsx new file mode 100644 index 0000000..728722d --- /dev/null +++ b/web/src/components/ErrorBoundary.jsx @@ -0,0 +1,74 @@ +import { Component } from "react"; +import Icon from "./Icon"; + +/** + * Fängt Fehler beim Rendern ab. + * + * Ohne so eine Grenze hängt React den kompletten Baum aus, sobald irgendwo eine + * Ausnahme fliegt – die Seite wird schlagartig weiß und nur ein Neuladen hilft. + * Man sieht dann NICHT, was passiert ist, und kann es folglich auch nicht + * melden. Genau das war hier das Problem. + * + * Die Grenze zeigt deshalb die Meldung selbst an, nicht nur ein „Ups". Wer sie + * einmal sieht, kann sie weitergeben, und dann ist die Ursache in einer Minute + * gefunden statt in einer Stunde. + * + * `bereich` beschreibt, was gekapselt ist – für die Meldung. + */ +export default class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { fehler: null, info: null }; + } + + static getDerivedStateFromError(fehler) { + return { fehler }; + } + + componentDidCatch(fehler, info) { + // React schreibt selbst schon in die Konsole; der Komponentenpfad hilft + // zusätzlich beim Einordnen. + console.error("Fehler beim Rendern:", fehler, info?.componentStack); + this.setState({ info }); + } + + render() { + const { fehler, info } = this.state; + if (!fehler) return this.props.children; + + return ( +
+
+ +
+
+ {this.props.bereich ? `${this.props.bereich} konnte nicht angezeigt werden.` : "Etwas ist schiefgelaufen."} +
+
+ {fehler.message || String(fehler)} +
+
+
+ +
+ + +
+ + {/* Aufgeklappt nur auf Wunsch: die Angaben sind für einen Fehlerbericht + gedacht, nicht für den Alltag. */} +
+ Technische Angaben +
+            {fehler.stack || String(fehler)}
+            {info?.componentStack || ""}
+          
+
+
+ ); + } +} diff --git a/web/src/main.jsx b/web/src/main.jsx index f85b0cb..8259879 100644 --- a/web/src/main.jsx +++ b/web/src/main.jsx @@ -8,12 +8,16 @@ import { ConfirmProvider } from "./confirm"; import { ToastProvider } from "./toast"; import "./styles.css"; import { applyFavicon } from "./branding"; +import ErrorBoundary from "./components/ErrorBoundary"; // Eigenes Favicon, falls hinterlegt - bewusst vor dem Login. applyFavicon(); ReactDOM.createRoot(document.getElementById("root")).render( + {/* Letzte Instanz: ohne sie haengt ein Fehler beim Rendern den ganzen + Baum aus und die Seite bleibt weiss. */} + @@ -25,5 +29,6 @@ ReactDOM.createRoot(document.getElementById("root")).render( + );