Use suspense / error boundary

This commit is contained in:
Adrian Rumpold
2025-05-22 08:13:43 +02:00
parent 7ccaa531e1
commit 0335e0cedb
6 changed files with 72 additions and 25 deletions

View File

@@ -6,16 +6,13 @@ import StringFilter from "./components/StringFilter/StringFilter";
import useGlossary from "./hooks/useGlossary"; import useGlossary from "./hooks/useGlossary";
function App() { function App() {
const { glossary, citations, isLoading, isError } = useGlossary(); const { glossary, citations } = useGlossary();
const [alphabeticalFilter, setAlphabeticalFilter] = useState(""); const [alphabeticalFilter, setAlphabeticalFilter] = useState("");
const [stringFilter, setStringFilter] = useState(""); const [stringFilter, setStringFilter] = useState("");
const [selectedCitation, setSelectedCitation] = useState<string | null>(null); const [selectedCitation, setSelectedCitation] = useState<string | null>(null);
if (isLoading) return <div>Loading...</div>; const entries = glossary
if (isError) return <div>Error while fetching</div>;
const entries = glossary!
.filter((entry) => { .filter((entry) => {
if (!stringFilter) { if (!stringFilter) {
return true; return true;
@@ -40,7 +37,7 @@ function App() {
{selectedCitation && ( {selectedCitation && (
<CitationEntry <CitationEntry
citation={ citation={
citations!.find((citation) => citation.key === selectedCitation)! citations.find((citation) => citation.key === selectedCitation)!
} }
onClose={() => setSelectedCitation(null)} onClose={() => setSelectedCitation(null)}
/> />

View File

@@ -0,0 +1,9 @@
.error-boundary {
color: red;
font-size: 1.2rem;
text-align: center;
pre {
color: var(--color-text);
}
}

View File

@@ -0,0 +1,43 @@
import type { ReactNode } from "react";
import React, { Component } from "react";
import "./ErrorBoundary.css";
interface ErrorBoundaryProps {
children: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
componentDidCatch(_: Error, __: React.ErrorInfo) {
// You can log the error to an error reporting service here
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<pre>{this.state.error?.message}</pre>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;

View File

@@ -1,13 +1,13 @@
import { useQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import { fetchCitations, fetchDefinitions } from "../lib/nist-api"; import { fetchCitations, fetchDefinitions } from "../lib/nist-api";
const useGlossary = () => { const useGlossary = () => {
const definitionsQuery = useQuery({ const definitionsQuery = useSuspenseQuery({
queryKey: ["definitions"], queryKey: ["definitions"],
queryFn: fetchDefinitions, queryFn: fetchDefinitions,
staleTime: 1000 * 60 * 60, // 1 hour staleTime: 1000 * 60 * 60, // 1 hour
}); });
const citationsQuery = useQuery({ const citationsQuery = useSuspenseQuery({
queryKey: ["citations"], queryKey: ["citations"],
queryFn: fetchCitations, queryFn: fetchCitations,
staleTime: 1000 * 60 * 60, // 1 hour staleTime: 1000 * 60 * 60, // 1 hour
@@ -16,8 +16,6 @@ const useGlossary = () => {
return { return {
glossary: definitionsQuery.data, glossary: definitionsQuery.data,
citations: citationsQuery.data, citations: citationsQuery.data,
isLoading: definitionsQuery.isLoading || citationsQuery.isLoading,
isError: definitionsQuery.isError || citationsQuery.isError,
}; };
}; };

View File

@@ -38,6 +38,7 @@ const citationGid = "2053825396";
const makeClient = () => { const makeClient = () => {
const client = new Axios({ const client = new Axios({
baseURL: baseUrl, baseURL: baseUrl,
timeout: 2500,
}); });
return client; return client;
}; };
@@ -62,18 +63,6 @@ export const fetchDefinitions = async () => {
return parseDefinitions(resp.data); return parseDefinitions(resp.data);
}; };
export const parseGlossary = (
glossaryData: string,
citationData: string
): Glossary => {
const glossary: Glossary = {
definitions: parseDefinitions(glossaryData),
citations: parseCitations(citationData),
};
return glossary;
};
const parseDefinitions = (glossaryData: string): GlossaryTerm[] => { const parseDefinitions = (glossaryData: string): GlossaryTerm[] => {
const terms: GlossaryTerm[] = []; const terms: GlossaryTerm[] = [];
const parsed = Papa.parse<string[]>(glossaryData, { const parsed = Papa.parse<string[]>(glossaryData, {

View File

@@ -1,15 +1,26 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { StrictMode } from "react"; import { StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import App from "./App.tsx"; import App from "./App.tsx";
import ErrorBoundary from "./components/ErrorBoundary/ErrorBoundary";
import "./index.css"; import "./index.css";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<App /> <ErrorBoundary>
<Suspense
fallback={
<div style={{ textAlign: "center", marginTop: "2rem" }}>
Loading...
</div>
}
>
<App />
</Suspense>
</ErrorBoundary>
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider> </QueryClientProvider>
</StrictMode> </StrictMode>