Получение "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, заранее спасибо.