Получение "Uncaught (in promise) TypeError: Failed to fetch" на локальном сервере (ReactJS)

Описание: У меня есть сайт на ReactJS, над которым я сейчас работаю. Есть LoginPage, в котором я отправляю POST запрос с Axios на бэкенд (Django). Я установил CORS_ALLOW_ALL_ORIGINS = True и веб-сайт работает просто отлично на компьютере без каких-либо журналов ошибок или предупреждений.

Проблема: Когда я пытаюсь протестировать его на андроид телефоне в той же сети (я использую vite и я установил все для разрешения соединений через локальную сеть) я получаю "Uncaught (in promise) TypeError: Failed to fetch". Это была боль в заднице $$$, чтобы на самом деле увидеть, что было консольным журналом в android, но с помощью некоторых сторонних приложений мне удалось выяснить, что это был журнал ошибок.

Ожидаемый результат: Поскольку я установил CORS на разрешение с любого хоста, и оба сервера backend и frontend запущены и разрешают подключения по локальной сети, такой ошибки быть не должно.


Используемые технологии: ReactJS 17.0.2, Vite 2.9.9, React router dom 6.3.0, Django 4.0.6.


Фрагменты кода:

LoginPage.jsx

import * as React from "react";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import CssBaseline from "@mui/material/CssBaseline";
import TextField from "@mui/material/TextField";
import FormControlLabel from "@mui/material/FormControlLabel";
import Checkbox from "@mui/material/Checkbox";
import Link from "@mui/material/Link";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import Typography from "@mui/material/Typography";
import Container from "@mui/material/Container";
import AuthContext from "../contexts/AuthContext";

export default function LoginPage() {
  const handleSubmit = (event) => {
    event.preventDefault();
    loginUser(event);
  };

  let { loginUser } = React.useContext(AuthContext);

  return (
    <Container component="main" maxWidth="xs">
      <CssBaseline />
      <Box
        sx={{
          marginTop: 8,
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
        }}
      >
        <Avatar sx={{ m: 1, bgcolor: "primary.main" }}>
          <LockOutlinedIcon />
        </Avatar>
        <Typography component="h1" variant="h5">
          Daxil ol
        </Typography>
        <Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
          <TextField
            margin="normal"
            required
            fullWidth
            id="username"
            label="İstifadəçi adı"
            name="username"
            autoComplete="username"
            autoFocus
          />
          <TextField
            margin="normal"
            required
            fullWidth
            name="password"
            label="Şifrə"
            type="password"
            id="password"
            autoComplete="current-password"
          />
          <FormControlLabel
            control={<Checkbox value="remember" color="primary" />}
            label="Məni xatırla."
          />
          <Button
            type="submit"
            fullWidth
            variant="contained"
            sx={{ mt: 3, mb: 2 }}
          >
            Daxil ol
          </Button>
          <Grid container>
            <Grid item xs>
              <Link href="#not-implemented-yet" variant="body2">
                Şifrəni unutmusan?
              </Link>
            </Grid>
            <Grid item>
              <Link href="/register" variant="body2">
                Hesabın yoxdur? Qeyd ol!
              </Link>
            </Grid>
          </Grid>
        </Box>
      </Box>
    </Container>
  );
}

AuthContext.jsx

import { createContext, useState, useEffect } from "react";
import jwt_decode from "jwt-decode";
import { useNavigate } from "react-router-dom";

const AuthContext = createContext();

export default AuthContext;

export const AuthProvider = ({ children }) => {
  function getFromStorage() {
    return localStorage.getItem("authTokens")
      ? JSON.parse(localStorage.getItem("authTokens"))
      : null;
  }

  let [authTokens, setAuthTokens] = useState(getFromStorage());
  let [user, setUser] = useState(getFromStorage());
  let [loading, setLoading] = useState(true);

  const navigate = useNavigate();

  let loginUser = async (e) => {
    const data = new FormData(e.currentTarget);
    let response = await fetch(
      import.meta.env.VITE_BACKEND_API_URL + "/api/token/",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          username: data.get("username"),
          password: data.get("password"),
        }),
      }
    );
    let responseData = await response.json();
    if (response.status === 200) {
      setAuthTokens(responseData);
      setUser(jwt_decode(responseData.access));
      localStorage.setItem("authTokens", JSON.stringify(responseData));
      navigate("/");
    } else {
      alert("Login Error!");
    }
  };

  let logoutUser = () => {
    setAuthTokens(null);
    setUser(null);
    localStorage.removeItem("authTokens");
    navigate("/login");
  };

  let contextData = {
    isAuthenticated: user != null ? true : false,
    user: user,
    setUser: setUser,
    loginUser: loginUser,
    logoutUser: logoutUser,
    authTokens: authTokens,
    setAuthTokens: setAuthTokens,
  };

  useEffect(() => {
    if (authTokens) {
      setUser(jwt_decode(authTokens.access));
    }
    setLoading(false);
  }, [authTokens, loading]);

  return (
    <AuthContext.Provider value={contextData}>
      {loading ? null : children}
    </AuthContext.Provider>
  );
};

useAxios.jsx

import axios from "axios";
import jwt_decode from "jwt-decode";
import dayjs from "dayjs";
import { useContext } from "react";
import AuthContext from "../contexts/AuthContext";

const useAxios = () => {
  const { authTokens, setUser, setAuthTokens } = useContext(AuthContext);

  const axiosInstance = axios.create({
    baseURL: import.meta.env.VITE_BACKEND_API_URL,
    headers: {
      Authorization: `Bearer ${authTokens?.access}`,
    },
  });

  axiosInstance.interceptors.request.use(async (req) => {
    const user = jwt_decode(authTokens?.access);
    const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;

    if (!isExpired) return req;

    const response = await axios.post(
      `${import.meta.env.VITE_BACKEND_API_URL}/api/token/refresh/`,
      {
        refresh: authTokens.refresh,
      }
    );

    localStorage.setItem("authTokens", JSON.stringify(response.data));

    setAuthTokens(response.data);
    setUser(jwt_decode(response.data.access));

    req.headers.Authorization = `Bearer ${response.data.access}`;
    return req;
  });

  return axiosInstance;
};

export default useAxios;

axiosInstance.jsx

import axios from "axios";
import jwt_decode from "jwt-decode";
import dayjs from "dayjs";

let authTokens = localStorage.getItem("authTokens")
  ? JSON.parse(localStorage.getItem("authTokens"))
  : null;

const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_BACKEND_API_URL,
  headers: {
    Authorization: `Bearer ${authTokens?.access}`,
  },
});

axiosInstance.interceptors.request.use(async (req) => {
  if (!authTokens) {
    authTokens = localStorage.getItem("authTokens")
      ? JSON.parse(localStorage.getItem("authTokens"))
      : null;
    req.headers.Authorization = `Bearer ${authTokens?.access}`;
  }

  const user = jwt_decode(authTokens.access);
  const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;

  if (!isExpired) {
    return req;
  } else {
    const response = await axios.post(
      import.meta.env.VITE_BACKEND_API_URL + "/api/token/refresh/",
      {
        refresh: authTokens.refresh,
      }
    );

    localStorage.setItem("authTokens", JSON.stringify(response.data));
    req.headers.Authorization = `Bearer ${response.data.access}`;
  }
  return req;
});

export default axiosInstance;

* Обратите внимание, я абсолютный новичок во всей среде ReactJS, заранее спасибо.

Вернуться на верх