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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <div className="center muted" style={{ padding: 60 }}>Lädt…</div>;
|
||||
if (startFehler) return <StartFehler text={startFehler} erneut={erneutVersuchen} />;
|
||||
if (!user) return <Navigate to="/login" replace />;
|
||||
@@ -147,7 +149,12 @@ function Protected({ children, adminOnly = false, wide = false }) {
|
||||
<Sidebar />
|
||||
<div className="main">
|
||||
{/* `wide` hebt die Lesebreite auf – nur fürs Dashboard-Raster sinnvoll. */}
|
||||
<div className={wide ? "content content--wide" : "content"}>{children}</div>
|
||||
<div className={wide ? "content content--wide" : "content"}>
|
||||
{/* 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. */}
|
||||
<ErrorBoundary key={pfad} bereich="Diese Seite">{children}</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
74
web/src/components/ErrorBoundary.jsx
Normal file
74
web/src/components/ErrorBoundary.jsx
Normal file
@@ -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 (
|
||||
<div style={{ padding: "var(--sp-4)" }}>
|
||||
<div className="alert error" style={{ alignItems: "flex-start" }}>
|
||||
<Icon name="alert" size={16} />
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div className="strong">
|
||||
{this.props.bereich ? `${this.props.bereich} konnte nicht angezeigt werden.` : "Etwas ist schiefgelaufen."}
|
||||
</div>
|
||||
<div className="small" style={{ marginTop: 4, wordBreak: "break-word" }}>
|
||||
{fehler.message || String(fehler)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field-inline" style={{ marginTop: "var(--sp-3)" }}>
|
||||
<button className="btn primary" onClick={() => this.setState({ fehler: null, info: null })}>
|
||||
Nochmal versuchen
|
||||
</button>
|
||||
<button className="btn" onClick={() => window.location.reload()}>
|
||||
Seite neu laden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Aufgeklappt nur auf Wunsch: die Angaben sind für einen Fehlerbericht
|
||||
gedacht, nicht für den Alltag. */}
|
||||
<details style={{ marginTop: "var(--sp-3)" }}>
|
||||
<summary className="muted small">Technische Angaben</summary>
|
||||
<pre className="muted small" style={{ whiteSpace: "pre-wrap", overflowX: "auto" }}>
|
||||
{fehler.stack || String(fehler)}
|
||||
{info?.componentStack || ""}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
<React.StrictMode>
|
||||
{/* Letzte Instanz: ohne sie haengt ein Fehler beim Rendern den ganzen
|
||||
Baum aus und die Seite bleibt weiss. */}
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<SettingsProvider>
|
||||
@@ -25,5 +29,6 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
</SettingsProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user