Skip to content

Instantly share code, notes, and snippets.

@peplocanto
Last active May 13, 2024 11:25
Show Gist options
  • Save peplocanto/88db751bdd8373241e1a078b0b3a27ed to your computer and use it in GitHub Desktop.
Save peplocanto/88db751bdd8373241e1a078b0b3a27ed to your computer and use it in GitHub Desktop.
How to gracefully handle runtime and http errors in react using axios, react query and react router dom 6

Error Handling

This error handling system differentiates between unexpected runtime errors and anticipated HTTP errors, managing them globally through an Error Boundary (provided by Reacr Router Dom) and locally, at component level, via React Query.

error_handling


Runtime Errors

Runtime errors are unexpected errors in the application's code that occur during execution. These are handled globally.

  • Global Handling: Implemented using React Router Dom's Error Boundary which catches errors in all child components, preventing the entire application from crashing and providing a user-friendly error message or a system recovery option.

HTTP Errors

HTTP errors occur when network requests via HTTP protocols result in error responses. Handling strategies for these errors are defined based on the error status code and can be managed globally or locally.

  • Global Handling with GlobalError Flag: Errors marked with a globalError flag are managed globally, using the application's Error Boundary. This method ensures that the application does not crash by locking it up and providing a system recovery option, similar to how runtime errors are handled.

  • Error Service Handling: HTTP errors without a globalError flag are assessed by a central error handling service. This service determines whether errors should be handled globally or locally based on predefined strategies. If an error is to be managed globally, actions such as logging out the user or redirecting to a custom error page are executed.

  • Local Handling through React Query: When the error service decides not to handle an error globally, it lets the error pass through to components using React Query. This allows individual components to handle errors locally, managing their own error states and rendering appropriate UI feedback.

This structured approach ensures that errors are managed efficiently, maintaining application integrity while enhancing the user experience by appropriately handling errors at both global and local levels.

http_error_handling


Error Handling Strategies

In the Väntar Admin Tool, HTTP error responses are processed based on predefined strategies tailored to the nature of the error and its impact on the application. Each strategy is designed to ensure that the system remains secure, stable, and user-friendly, even when unexpected issues arise. Here's a detailed explanation of how different errors are managed:

  • Error Page Strategy ( default strategy ): For critical server errors (status codes 500 and 502), the system displays a generic error page. This page informs the user of the issue and offers an option to refresh the page, hoping to resolve the issue upon reload.

  • Logout Strategy: If a response returns a 401 (Unauthorized) or 403 (Forbidden), it indicates an authentication or authorization issue. The system responds by logging the user out to prevent any unauthorized access, ensuring that all session information is cleared.

  • Not Found Strategy: Encountering a 404 status code triggers a redirection to a 'Not Found' page. This strategy helps guide users away from invalid URLs back to a valid route within the application, maintaining a fluid user experience. This is a global strategy, used for missing parts of the application as modules or microfrontends, missing data are treathed locally.

  • Retry Strategy: For errors that are potentially recoverable, such as a 503 Service Unavailable, the system attempts to automatically retry the request. This is based on the assumption that the issue may be temporary, like a server being momentarily overloaded.

  • Ignore Strategy: When an error does not critically affect the application's operation or security, such as a 400 Bad Request, it is passed to local handling mechanisms. This allows components that initiated the request to handle the error directly, displaying context-specific messages or fallback content.

These strategies are orchestrated by the central Error Service, which dynamically applies the appropriate response based on the error's context and the global or local flags associated with each request. This approach ensures a robust handling of issues while minimizing disruption to the user experience.

Local Error Handling Implementation Example

Here's how a typical data-fetching component might handle errors locally:

const DataPage = () => {
  const { data, loading, error } = useQuery(GET_DATA);
  return (
    error ? <ErrorComponent error={error} /> :
    loading ? <LoadingComponent /> :
    <DataComponent data={data} />
  );
};

Handling other HTTP error codes & adding new Strategies

Error handling strategies follow the open closed principle. To handle a new http error code or to add a new strategy the ERROR_HANDLING_STRATEGIES object in error.model.ts file has to be modified.

export const ERROR_HANDLING_STRATEGIES = {
  logout: [401, 403],
  notFound: [404],
  retry: [503],
  errorPage: [500, 502],
  ignore: [400],
} as const;

export const DEFAULT_ERROR_MESSAGE = 'defaultErrorMessage';
export const DEFAULT_ERROR_HANDLING_STRATEGY: ErrorHandlingStrategy = 'errorPage';

export const ERROR_HANDLING_STRATEGY_MAP = Object.entries(ERROR_HANDLING_STRATEGIES) as unknown as [
  ErrorHandlingStrategy,
  HandledErrorCode[],
][];
export const HANDLED_ERROR_CODES = Object.values(ERROR_HANDLING_STRATEGIES).flat();

export type ErrorHandlingStrategy = keyof typeof ERROR_HANDLING_STRATEGIES;
export type ErrorHandler = `${ErrorHandlingStrategy}Strategy`;
export type HandledErrorCode = (typeof HANDLED_ERROR_CODES)[number];

export const isHandledErrorCode = (code: number): code is HandledErrorCode =>
  HANDLED_ERROR_CODES.includes(code as HandledErrorCode);

To handle a new http error code just add the code to an existing strategy array.

To add a new strategy add a new unique key ant then create a new handler in error.service.ts file.

export function errorHandler(message: string, strategy: ErrorHandlingStrategy = DEFAULT_ERROR_HANDLING_STRATEGY) {
  return STRATEGIES[strategy](message);
}

export function httpErrorHandler(error: AxiosError, override?: ErrorHandlingStrategy) {
  const message = getHttpErrorMessage(error);
  const strategy = override ?? getStrategy(error.response?.status);
  return errorHandler(message, strategy);
}

function getHttpErrorMessage(error: AxiosError) {
  return (
    error.message ??
    error.response?.statusText ??
    (error.response?.data as { message: string })?.message ??
    DEFAULT_ERROR_MESSAGE
  );
}

function getStrategy(status?: number): ErrorHandlingStrategy {
  if (!status) return DEFAULT_ERROR_HANDLING_STRATEGY;
  if (isHandledErrorCode(status)) {
    for (const [strategy, codes] of ERROR_HANDLING_STRATEGY_MAP) {
      if (codes.includes(status)) {
        return strategy;
      }
    }
  }
  return DEFAULT_ERROR_HANDLING_STRATEGY;
}

const STRATEGIES = ERROR_HANDLING_STRATEGY_MAP.reduce(
  (acc, [strategy]) => {
    acc[strategy] = HANDLERS[`${strategy}Strategy`];
    return acc;
  },
  {} as Record<ErrorHandlingStrategy, (message: string) => void>
);

const HANDLERS: Record<ErrorHandler, (message: string) => void> = {
  logoutStrategy,
  notFoundStrategy,
  retryStrategy,
  errorPageStrategy,
  ignoreStrategy,
  // Add here new error handling strategies
};

function logoutStrategy(message: string) {
  console.error(message);
  RoutingService.navigateTo(ROUTES.logout);
}

function notFoundStrategy(message: string) {
  console.error(message);
  RoutingService.navigateTo(ROUTES.notFound);
}

function retryStrategy(message: string) {
  // retry strategy
}

function errorPageStrategy(message: string) {
  throw new Error(message);
}

function ignoreStrategy(message: string) {
  console.log('Ignored', message);
}
@peplocanto
Copy link
Author

error_handling
http_error_handling

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment