我正在尝试将 AuthContext 设置为 React,但遇到了一个问题:React 在 axios 设置请求中的 auth 标头之前调用 API。这会导致首次渲染时 API 失败
missing authentication parameters
。
我将提供
AuthContext
、AuthService
和 App
的较小示例
AuthContext
import React, { createContext, useState, useEffect, useContext, useCallback } from "react";
import AuthService from "./authService";
// Hardcoded token for testing
const hardcodedToken = "your-hardcoded-token-here";
// Create the AuthContext
const AuthContext = createContext(null);
export const AuthContextProvider = ({ children }) => {
const [token, setToken] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const checkIfLogged = useCallback(async () => {
const token = hardcodedToken;
if (token) {
setToken(token);
setIsAuthenticated(true);
// Set Axios defaults and mark auth as ready
authService.setAxiosDefaults(token);
} else {
setIsAuthenticated(false);
authService.TODOLogout();
}
}, []);
useEffect(() => {
checkIfLogged();
}, [checkIfLogged]);
return (
<AuthContext.Provider value={{ token, isAuthenticated }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
AuthService
import axios from "axios";
class AuthService {
constructor(authRedirectUrlPrefix) {
this.authRedirectUrlPrefix = authRedirectUrlPrefix;
}
/** Set default axios headers and interceptors */
setAxiosDefaults(token) {
debugger;
// Pass auth token in each request
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
axios.defaults.headers.common["Content-Type"] = "application/json";
axios.interceptors.response.use(
(response) => {
return response; // Ignore successful responses
},
(error) => {
// If response contains auth error, redirect to login
if (error.response.status === 401) {
this.redirectToLogout(false);
}
return error;
}
);
}
}
export default AuthService;
App
import "./App.css";
import { Route, Switch, Redirect, BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import Container from "@mui/material/Container";
import Header from "./layout/Header";
import store from "./store";
import { Home } from "./components/Home";
import { AuthContextProvider } from "./auth/AuthContext";
import { ApiContextProvider } from "./context/ApiContext";
const mdTheme = createTheme();
function App() {
return (
<AuthContextProvider>
<ApiContextProvider>
<BrowserRouter>
<ThemeProvider theme={mdTheme}>
<Box sx={{ display: "flex" }}>
<CssBaseline />
<Header />
<Box
component="main"
sx={{
backgroundColor: (theme) =>
theme.palette.mode === "light"
? theme.palette.grey[300]
: theme.palette.grey[900],
flexGrow: 1,
height: "100vh",
overflow: "auto",
}}
>
<Toolbar />
<Container maxWidth="lg" sx={{ mt: 3, mb: 3 }}>
<Switch>
<Route exact path="/" component={Home} />
<Redirect from="*" to="/" />
</Switch>
</Container>
</Box>
</Box>
</ThemeProvider>
</BrowserRouter>
</ApiContextProvider>
</AuthContextProvider>
);
}
export default App;
现在,考虑到所有这些设置,发生的情况是我的 React 应用程序在设置 auth 标头之前调用第一个 api 请求。我可以这么说,因为我在这里添加了一个
debugger
,并且在重新加载应用程序时首先会点击它。
import React, { createContext, useContext, useMemo } from "react";
import axios from "axios";
// import config from "../config";
const ApiClient = {
getData: async () => {
// debugger;
const response = await axios.get(/demo/data);
return response.data;
},
};
// Create the API Client Context
export const ApiClientContext = createContext(ApiClient);
export const useApiClient = () => {
return useContext(ApiClientContext);
};
// Provider component to wrap around your app
export const ApiContextProvider = ({ children }) => {
const value = useMemo(() => {
return ApiClient;
}, []);
return (
<ApiContextProvider.Provider value={value}>
{children}
</ApiContextProvider.Provider>
);
};
问题的原因似乎是在
token
初始化为 null
和更新为正确值之间的短时间内发出第一个请求。这是一个完全有效的场景。为了避免这种情况,我们可以在发出请求之前获取 const { isAuthenticated } = useAuth();
并等待 isAuthenticated
变为 true
。
以下是实现此目的的一种方法。
const ApiClient = (isAuthenticated) => ({
getData: async () => {
if (isAuthenticated) {
const response = await axios.get(/demo/data);
return response.data;
} else {
// don't call the endpoint when not authenticated
return undefined;
}
}),
};
export const ApiContextProvider = ({ children }) => {
const { isAuthenticated } = useAuth();
const value = useMemo(() => {
return ApiClient(isAuthenticated);
}, [isAuthenticated]);
return (
<ApiContextProvider.Provider value={value}>
{children}
</ApiContextProvider.Provider>
);
};