Error handling based on react Redux

Time:2020-10-23

This paper is divided into three parts

  • Classification of error
  • Explain in detail how to use Redux to deal with errors in a unified way step by step
  • Collection of error information

The technology stack used in this case includes:ReactReduxTypeScriptAxiosLodash

Classification of error

HTTP request error

HTTP request errors can be classified into the following categories:

The server has a response error

The server has a response, indicating that the server has responded and returned the corresponding error message

If you do not expect each request to display a specific error message returned by the server, you can further classify the error information according to the HTTP status code:

  • 4xx client error: indicates that an error has occurred on the client, hindering the processing of the server。 For example:

    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
    • 408 Request Timeout
    • 409 Conflict
  • 5xx server error: indicates that the server was unable to complete a legitimate request。 It may be that the server has an error or an abnormal state in the process of processing the request. For example:

    • 500 Internal Server Error
    • 501 Not Implemented
    • 503 Service Unavailable

Server unresponsive error

The server does not respond, indicating that the request was initiated, but the server did not respond

This situation may be due to network failure (no network / weak network), or cross domain request is rejected (production environment usually does not have cross domain situation, so this error is generally not considered). If the HTTP client you are using does not return an error message, consider displaying a general error message.

application error

Code error

Usually, the JavaScript engine can’t execute correctly due to the error of JS code writing, so an error is reported. This kind of error usually does not appear in the production environment, so you can decide whether to handle this kind of error according to the business requirements. The common ones are:

  • Syntax error in syntax
  • Referenceerror
  • Typeerror type error

Throw Error

The error that is thrown according to the business requirements in the application.

Error handling in Redux

In the above section, we have classified the error in the application. With Redux, we can handle HTTP request errors uniformly.

Step1: fission HTTP request action

When making an HTTP request, we usually initiate an action. If you split the success and failure status of a request into two actions,RequestSuccessActionandRequestFailedActionThrough the requestfailed action, the errors of all HTTP requests can be handled uniformly.

requestMiddleware.ts

export const requestMiddleware: any = (client: AxiosInstance) => {
  return ({ dispatch }: MiddlewareAPI<any>) =>
    (next: Dispatch<any>) =>
      (action: IRequestAction) => {
        if (isRequestAction(action)) {
          dispatch(createReqStartAction(action));
          return client.request(action.payload)
            .then((response: AxiosResponse) => {
              return dispatch(createSuccessAction(action, response));
            })
            .catch((error: AxiosError) => {
              return dispatch(createFailedAction(action, error, action.meta.omitError));
            });
        }
        return next(action);
      };
};

Step 2: create errormiddleware and convert error to notification action

After converting the failure status of an HTTP request to a requestfailed action, we need to write a middleware to handle it.

Some people may ask, since there is already a request failed action, do you still need middleware? Can you deal with it directly in reducer? In fact, it can be. However, if multiple state nodes are modified by the same action in the reducer, the code coupling will increase, so we still use the middleware method to deal with it here. The ideas are as follows:

  1. If the action is a request failed action, the type and information of the error are stored in theaddNotificationActionMedium. We don’t need to save all the error information here, because the UI only cares about the type and information of the error.
  2. According to the classification of error, dispatch has actions with different error types and error messages.
  3. establishcreateNotificationFunction to generate a notification with UUID for deletion. Because there may be more than one notification.
  4. Through dispatchremoveNotificationActionTo remove the notification.
export interface INotification {
  [UUID: number]: {
    type: string;
    msg: string;
  };
}

const createNotification = ({ type, msg }: { type: string; msg: string }): INotification => {
  const id = new Date().getTime();
  return {
    [id]: {
      type,
      msg,
    },
  };
};

The complete code is as follows:

errorMiddleware.ts

import { AnyAction, Dispatch, MiddlewareAPI } from "redux";
import { isRequestFailedAction } from "../request";
import {
  addNotification,
  INotification,
} from "./notificationActions";

export enum ErrorMessages {
  GENERAL_ERROR = "Something went wrong, please try again later!",
}

enum ErrorTypes {
  GENERAL_ERROR = "GENERAL_ERROR",
}

export const createNotification = ({ type, msg }: { type: string; msg: string }): INotification => {
  const id = new Date().getTime();
  return {
    [id]: {
      type,
      msg,
    },
  };
};

export const errorMiddleware = ({ dispatch }: MiddlewareAPI) => {
  return (next: Dispatch<AnyAction>) => {
    return (action: AnyAction) => {
      if (isRequestFailedAction(action)) {
        const error = action.payload;
        if (error.response) {
          dispatch(
            addNotification(
              createNotification({
                type: error.response.error,
                msg: error.response.data.message,
              }),
            ),
          );
        } else {
          dispatch(
            addNotification(
              createNotification({
                type: ErrorTypes.GENERAL_ERROR,
                msg: ErrorMessages.GENERAL_ERROR,
              }),
            ),
          );
        }
      }
      return next(action);
    };
  };
};

notificationActions.ts

import { createAction } from "redux-actions";

export interface INotification {
  [UUID: number]: {
    type: string;
    msg: string;
  };
}

export const addNotification = createAction(
  "@@notification/addNotification",
  (notification: INotification) => notification,
);

export const removeNotification = createAction("@@notification/removeNotification", (id: number) => id);

export const clearNotifications = createAction("@@notification/clearNotifications");

The server needs to ensure that each http reqeust has the corresponding error message, otherwise the front-end can only display the error message according to the rough classification of 4xx or 5xx.

Step3: handle notification action

notificationsReducer.ts

import { omit } from "lodash";
import { Action, handleActions } from "redux-actions";
import { addNotification, clearNotifications, removeNotification } from "./notificationActions";

export const notificationsReducer = handleActions(
  {
    [`${addNotification}`]: (state, action: Action<any>) => {
      return {
        ...state,
        ...action.payload,
      };
    },
    [`${removeNotification}`]: (state, action: Action<any>) => {
      return omit(state, action.payload);
    },
    [`${clearNotifications}`]: () => {
      return {};
    },
  },
  {},
);

Step4: get the notification from the store and provide it to the subcomponents through react child render.

This step is very simple. Get the notifications from the store, and then provide it to the sub component through react child render. The sub component can display the UI according to it.

WithNotifications.tsx

import { isEmpty } from "lodash";
import * as React from "react";
import {
  connect,
  DispatchProp,
} from "react-redux";
import {
  clearNotifications,
  INotification,
} from "./notificationActions";

interface IWithNotificationsCoreInnerProps {
  notifications: INotification;
}

interface IWithNotificationsCoreProps extends DispatchProp {
  notifications: INotification;
  children: (props: IWithNotificationsCoreInnerProps) => React.ReactNode;
}

class WithNotificationsCore extends React.Component<IWithNotificationsCoreProps> {
  componentWillUnmount() {
    this.props.dispatch(clearNotifications());
  }

  render() {
    if (isEmpty(this.props.notifications)) {
      return null;
    }

    return this.props.children({
      notifications: this.props.notifications,
    });
  }
}

const mapStateToProps = (state: any) => {
  return {
    notifications: state.notifications,
  };
};

export const WithNotifications = connect(mapStateToProps)(WithNotificationsCore);

Step5: display error messages

Since notification is a common component, we usually put it on the root component.

<WithNotifications>
  {({ notifications }) => (
    <>
      {map(notifications, (notification: { type: string; msg: string }, id: number) => {
        return (
          <div>
            { notification.msg }// put your notification component here            
            {ID} // you can use ID to delete the corresponding notification
          </div>
        );
      })}
    </>
  )}
</WithNotifications>

Step 6: add white list

Of course, we don’t need to notify users of all API requests that go wrong. At this time, you need to add a white list. If it is in the white list, the user will not be informed of the error information. Consider adding a meta in requst actionomitErrorWhen there is a flag, there is no notification. Let’s modify error middleware as follows:

errorMiddleware.ts

export const errorMiddleware = ({ dispatch }: MiddlewareAPI) => {
  return (next: Dispatch<AnyAction>) => {
    return (action: AnyAction) => {
    const shouldOmitError = get(action, "meta.omitError", false);
      if (isRequestFailedAction(action) && !shouldOmitError) {
        const error = action.payload;
        if (error.response) {
          // same as before
        } else {
          // same as before
      }
      return next(action);
    };
  };
};

STEP7: testing

When testing errormiddleware, we may encounter a problem, that is, our notification is based on an object with a timestamp as the key, and the timestamp is generated according to the current time. Every time we run the test, it will change. How to solve this problem? The mock gettime method is fine. As follows:

 beforeEach(() => {
    class MockDate {
      getTime() {
        return 123456;
      }
    }

    global.Date = MockDate as any;
  });

  afterEach(() => {
    global.Date = Date;
  });

Collection of error information

componentDidCatch

Using reactcomponentDidCatchThe lifecycle method collects error information to the error reporting service. This method is a bit like JScatch{}, just for components. Most of the time, we want the errorboundary component to run through our entire application, so we usually put it on the root node.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

Note: for the errorboundary component, it only captures the components under it, and it does not capture errors within its own components.

Recommended Today

Application case of tsutsuga: big data judicial query platform

1. Preface When the public security organs, procuratorial organs and law enforcement organs need to inquire about the bank deposits of enterprises, institutions, institutions and organizations or consult the accounting vouchers, account books, statements and other archives related to the case, the banks shall actively cooperate. When inquiring or consulting, the people’s court shall issue […]