Skip to content

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