Error Handling¶
Client-side error handling and display patterns.
Overview¶
flowchart TB
A[API Response] --> B{Status Code?}
B -->|2xx| C[Success Handler]
B -->|4xx/5xx| D[Error Handler]
D --> E[Parse Error]
E --> F{Error Type?}
F -->|Validation| G[Field Errors]
F -->|Auth| H[Redirect to Login]
F -->|Other| I[Toast/Alert]
G --> J[Form Display]
I --> K[User Notification] HttpError Class¶
Structured error from API responses:
class HttpError extends Error {
status: number;
code: string;
fieldErrors?: Record<string, string[]>;
retryAfter?: number;
correlationId?: string;
}
Error Parsing¶
API client parses error responses:
async function handleResponse(response: Response) {
if (!response.ok) {
const data = await response.json();
throw new HttpError({
status: response.status,
code: data.code,
message: data.message,
fieldErrors: data.errors,
correlationId: data.correlationId,
});
}
return response.json();
}
Form Validation Errors¶
Map API errors to form fields:
const onSubmit = async (data: FormData) => {
try {
await createMarker(data);
} catch (error) {
if (isHttpError(error) && error.fieldErrors) {
Object.entries(error.fieldErrors).forEach(([field, messages]) => {
setError(field, { message: t(messages[0]) });
});
} else {
toast.error(t("unexpectedError"));
}
}
};
Error Guards¶
Utility functions for error handling:
function isHttpError(error: unknown): error is HttpError {
return error instanceof HttpError;
}
function shouldIgnoreError(error: unknown): boolean {
if (!isHttpError(error)) return false;
return error.status === 401 && isAuthEndpoint(error);
}
function isAuthEndpoint(error: HttpError): boolean {
return error.config?.url?.includes("/auth/");
}
Toast Notifications¶
For non-field errors:
try {
await deleteMarker(id);
toast.success(t("markerDeleted"));
} catch (error) {
toast.error(getErrorMessage(error));
}
Helper for user-friendly messages:
function getErrorMessage(error: unknown): string {
if (isHttpError(error)) {
return t(`errors.${error.code}`, { defaultValue: t("unexpectedError") });
}
return t("unexpectedError");
}
Error Boundary¶
Catches rendering errors:
<ErrorBoundary
fallback={({ error, resetErrorBoundary }) => (
<ErrorFallback
error={error}
onRetry={resetErrorBoundary}
/>
)}
onError={(error, info) => {
captureException(error, { extra: info });
}}
>
<App />
</ErrorBoundary>
Development mode shows stack trace; production shows friendly message.
Query Error Handling¶
TanStack Query error handling:
const { data, error, isError } = useQuery({
queryKey: ["markers"],
queryFn: fetchMarkers,
retry: (failureCount, error) => {
if (isHttpError(error) && error.status < 500) {
return false; // Don't retry client errors
}
return failureCount < 3;
},
});
if (isError) {
return <ErrorState error={error} />;
}
Mutation Error Handling¶
const mutation = useMutation({
mutationFn: createMarker,
onError: (error) => {
if (isHttpError(error) && error.status === 409) {
toast.error(t("markerConflict"));
} else {
toast.error(t("unexpectedError"));
}
},
});
Sentry Integration¶
Report errors to Sentry:
import * as Sentry from "@sentry/react";
function reportError(error: Error, context?: Record<string, unknown>) {
Sentry.captureException(error, {
extra: context,
tags: {
correlationId: error instanceof HttpError ? error.correlationId : undefined,
},
});
}
Error Display Patterns¶
| Error Type | Display Method |
|---|---|
| Field validation | Inline field errors |
| Business rule | Toast notification |
| Not found | Redirect to 404 page |
| Unauthorized | Redirect to login |
| Server error | Error page/toast |