首页 > 解决方案 > 使用 Axios 在反应中进行错误处理的最佳方法是什么?

问题描述

我正在开发一个 React 应用程序。无论是 Angular/React/Vue 项目,我每次都面临的问题是 AJAX 请求期间的错误处理(可以使用 Axios 或任何其他 AJAX 库进行)

我在这里找到的大多数答案都只是在console.log(error)ortry catch方法Promise.reject()中进行。现实世界的应用程序不是这样工作的。

我想迎合所有可能的错误,并根据错误的类型向用户显示适当的消息。例如,错误可能是:

我怎样才能知道所有可能的错误情况?重要的是,制作它们以查看我的应用程序如何响应它们中的每一个。

由于我也在开发 API,请让我知道我应该如何将错误发送回前端,以便它可以适当地处理它们。

目前,我的实现是(这不是很好的 IMO):


import React from "react";
import Link from "../../components/AppLink/AppLink";
import AppTextField from "../../components/FormComponents/AppTextField";
import styles from "../../styles/Auth.module.css";
import { Box, Button, Grid, Hidden, InputAdornment, Typography, Collapse  } from "@material-ui/core";
import { Alert, AlertTitle } from '@material-ui/lab';
import EmailSvgIcon from "../../components/Icons/EmailSvgIcon";
import LockSvgIcon from "../../components/Icons/LockSvgIcon";
import LockOpenIcon from '@material-ui/icons/LockOpen';
import { ROUTE_FORGOT_PASSWORD, ROUTE_REGISTER } from "../../utils/RoutesList";
import AuthBanner from "../../components/AuthBanner/AuthBanner";
import useIsMobile from "../../hooks/useIsMobile";
import * as yup from 'yup';
import axios from '../../axiosConfig';
import CircularProgress from '@material-ui/core/CircularProgress';
import { useState } from "react";
import { AlertProps } from "@material-ui/lab";
import { mapApiErrorsToHookForm } from "../../utils/Form";
import { AxiosError } from "axios";
import { useForm, FormProvider } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers/yup';

interface FormValues
{
    email: string;
    password: string;
}

interface AlertState
{
    severity : AlertProps["severity"];
    title    : string;
    message  : string;
    show     : boolean;
}

const validationSchema = yup.object().shape({
    email: yup.string()
        .required("Please enter your email")
        .email("Please enter a valid email address"),
    password: yup.string().required("Please enter your password"),
});

const LoginPage = () =>
{
    const formMethods = useForm <FormValues>({
        resolver: yupResolver(validationSchema)
    });

    const { isSubmitting } = formMethods.formState;
    const isMobile = useIsMobile();

    const [alert, setAlert] = useState <AlertState>({
        severity : undefined,
        title    : "",
        message  : "",
        show     : false,
    });

    const onSubmit = async (formValues: any) =>
    {
        try
        {
            const response = await axios.post("auth/user/login", formValues);
            
        }
        catch (error)
        {
            const exception: AxiosError = error;

            let title = "Error", message: string;

            if (exception.response && exception.response.status === 400)
            {
                message = exception.response.data.message;

                switch (exception.response.data.errorType)
                {
                    case "VALIDATION_ERROR":
                        title = "Incorrect input";
                        
                        let errors = mapApiErrorsToHookForm(exception.response.data.errors)

                        for (let field in errors)
                        {
                            formMethods.setError(field as keyof FormValues, {
                                type: "validation",
                                message: errors[field]
                            });
                        }

                        break;

                    case "ACCOUNT_NOT_FOUND":
                        title = "Account not found";
                        break;
                }
            }
            else
            {
                message = exception.message;
            }

            setAlert({
                title,
                message,
                severity: "error",
                show: true
            });
        }
    }

    return (
        <>
            <Grid
                item
                container
                xs={12}
                sm={4}
                direction="column"
                justify="space-between"
                className={styles.bannerGrid}>
                <AuthBanner
                    image="/images/auth/login_banner.jpg"
                    title="Tip"
                    description="The search for housing on this site is 10 times faster"
                />
            </Grid>
            <Grid item container sm={8} className={styles.contentWrapper}>
                <Box display="flex" flexDirection="column" flex={1} paddingX={2} paddingY={2}>
                    <Hidden only="xs">
                        <Box textAlign="right">
                            <Link
                                underline="none"
                                href={ROUTE_REGISTER}
                                color="textPrimary"
                                className={styles.topLink}>
                                Register
                            </Link>
                        </Box>
                    </Hidden>
                    <Box
                        display="flex"
                        flexDirection="column"
                        justifyContent="center"
                        flex={1}
                        maxWidth={500}>
                        <Box marginBottom={3}>
                            <Box marginBottom={1.5}>
                                <Typography variant="h4" className={styles.formTitle}>Login</Typography>
                            </Box>
                            <Typography variant="body1" color="textSecondary">Welcome back!</Typography>
                        </Box>
                        <FormProvider {...formMethods}>
                            <form onSubmit={formMethods.handleSubmit(onSubmit)}>
                                <Collapse in={alert.show && !isSubmitting}>
                                    <Box marginBottom={3}>
                                        <Alert
                                            severity={alert.severity}
                                            onClose={() => setAlert({...alert, show: false})}>
                                            <AlertTitle>{alert.title}</AlertTitle>
                                            {alert.message}
                                        </Alert>
                                    </Box>
                                </Collapse>
                                <Box marginBottom={2}>
                                    <AppTextField
                                        name="email"
                                        type="email"
                                        label="Email"
                                        variant="outlined"
                                        fullWidth
                                        InputProps={{
                                            endAdornment: (
                                                <InputAdornment position="end">
                                                    <EmailSvgIcon fontSize="small" />
                                                </InputAdornment>
                                            ),
                                        }}
                                    />
                                </Box>
                                <Box marginBottom={2}>
                                    <AppTextField
                                        name="password"
                                        type="password"
                                        label="Password"
                                        variant="outlined"
                                        fullWidth
                                        InputProps={{
                                            endAdornment: (
                                                <InputAdornment position="end">
                                                    <LockSvgIcon fontSize="small" />
                                                </InputAdornment>
                                            ),
                                        }}
                                    />
                                </Box>
                                <Grid container spacing={2}>
                                    <Grid
                                        item xs={12}
                                        sm={6}>
                                        <Button
                                            type="submit"
                                            variant="contained"
                                            color="primary"
                                            size="large"
                                            endIcon={
                                                isSubmitting
                                                    ? <CircularProgress color="secondary" size={18} />
                                                    : <LockOpenIcon />
                                            }
                                            fullWidth={isMobile}
                                            disabled={isSubmitting}>
                                            Login
                                        </Button>
                                    </Grid>
                                    <Grid
                                        item
                                        container
                                        xs={12}
                                        sm={6}
                                        alignItems="center"
                                        justify={isMobile ? "center" : "flex-end"}>
                                        <Link
                                            href={ROUTE_FORGOT_PASSWORD}
                                            color="primary"
                                            className={styles.formLink}>
                                            Forgot Password?
                                        </Link>
                                    </Grid>
                                </Grid>
                            </form>
                        </FormProvider>
                    </Box>
                </Box>
            </Grid>
        </>
    );
}

export default LoginPage;


对于这个特定的表单,我errorType从 API 发送并基于它,我设置了相应的警报标题和消息,但是我需要为每个表单都这样做吗?

我知道拦截器,但它无法在每个错误字段下显示验证消息,因为formMethods它无法在其上下文中显示。

请提供带有示例的详细答案,以便我对此有很好的理解。另外,不要忘记解释如何处理所有可能的错误情况并处理它们。

提前致谢 :)

标签: reactjsaxios

解决方案


您可以创建一个Axios实例,并像这样全局处理一些错误

const axiosInstance = Axios.create({
  baseURL: apiEndpoint,
  headers: {
    'Content-Type': 'application/json',
  },
});

axiosInstance.interceptors.response.use(undefined, error => {
  const statusCode = error.response ? error.response.status : null;

  if (statusCode === 401) {
    // logout user
  }

  if (statusCode >= 500) {
    // show server error
  }

  if (statusCode === 400) {
    // show bad request error
  }

  return Promise.reject(error);
});
 

然后,如果您需要,您还可以在组件级别进行处理。


推荐阅读