Mis*_*a M 11 javascript reactjs react-router react-router-dom
我有一个基于 JWT 的 API。它会在每次响应时轮换令牌。我有一个自定义提供者来管理这个。
我试图弄清楚如何通过此设置使用 React Router v6.4 数据路由器。具体来说,我想使用loader/action函数来获取数据,但这些函数不支持useContext,我不知道如何传递它。
我想dashboardLoader使用当前的令牌集作为为我管理的标头来调用 API AuthContext。
目标是让该loader函数获取一些数据以显示在仪表板上并使用get()来自AuthProvider.
我当前的选择是在组件内部执行此操作Dashboard,但想了解如何使用加载程序执行此操作。
相关文件:
// App.js
import "./App.css";
import { createBrowserRouter } from "react-router-dom";
import "./App.css";
import Dashboard, { loader as dashboardLoader } from "./dashboard";
import AuthProvider from "./AuthProvider";
import axios from "axios";
function newApiClient() {
return axios.create({
baseURL: "http://localhost:3000",
headers: {
"Content-Type": "application/json",
},
});
}
const api = newApiClient();
export const router = createBrowserRouter([
{
path: "/",
element: (<h1>Welcome</h1>),
},
{
path: "/dashboard",
element: (
<AuthProvider apiClient={api}>
<Dashboard />
</AuthProvider>
),
loader: dashboardLoader,
},
]);
Run Code Online (Sandbox Code Playgroud)
// AuthProvider
import { createContext, useState } from "react";
const AuthContext = createContext({
login: (email, password) => {},
isLoggedIn: () => {},
get: async () => {},
post: async () => {},
});
export function AuthProvider(props) {
const [authData, setAuthData] = useState({
client: props.apiClient,
accessToken: "",
});
async function login(email, password, callback) {
try {
const reqData = { email: email, password: password };
await post("/auth/sign_in", reqData);
callback();
} catch (e) {
console.error(e);
throw e;
}
}
function isLoggedIn() {
return authData.accessToken === "";
}
async function updateTokens(headers) {
setAuthData((prev) => {
return {
...prev,
accessToken: headers["access-token"],
};
});
}
async function get(path) {
try {
const response = await authData.client.get(path, {
headers: { "access-token": authData.accessToken },
});
await updateTokens(response.headers);
return response;
} catch (error) {
console.error(error);
throw error;
}
}
async function post(path, data) {
try {
const response = await authData.client.post(path, data, {
headers: { "access-token": authData.accessToken },
});
await updateTokens(response.headers);
return response.data;
} catch (error) {
// TODO
console.error(error);
throw error;
}
}
const context = {
login: login,
isLoggedIn: isLoggedIn,
get: get,
post: post,
};
return (
<AuthContext.Provider value={context}>
{props.children}
</AuthContext.Provider>
);
}
Run Code Online (Sandbox Code Playgroud)
// Dashboard
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import AuthContext from "./AuthProvider";
export function loader() {
// TODO use auth context to call the API
// For example:
// const response = await auth.get("/my-data");
// return response.data;
}
export default function Dashboard() {
const auth = useContext(AuthContext);
if (!auth.isLoggedIn()) {
return <Navigate to="/" replace />;
}
return <h1>Dashboard Stuff</h1>;
}
Run Code Online (Sandbox Code Playgroud)
按原样创建 axios 实例,但您将调整AuthProvider以添加请求和响应拦截器来处理令牌和标头。apiClient您还将传递对加载器函数的引用dashboardLoader。
认证提供者
将访问令牌存储在 React ref 中,并直接使用/引用传递的内容,apiClient而不是将其存储在本地组件状态中(React 反模式)。添加一个useEffect钩子来添加请求和响应拦截器来维护该accessTokenRef值。
export function AuthProvider({ apiClient }) {
const accessTokenRef = useRef();
useEffect(() => {
const requestInterceptor = apiClient.interceptors.request.use(
(config) => {
// Attach current access token ref value to outgoing request headers
config.headers["access-token"] = accessTokenRef.current;
return config;
},
);
const responseInterceptor = apiClient.interceptors.response.use(
(response) => {
// Cache new token from incoming response headers
accessTokenRef.current = response.headers["access-token"];
return response;
},
);
// Return cleanup function to remove interceptors if apiClient updates
return () => {
apiClient.interceptors.request.eject(requestInterceptor);
apiClient.interceptors.response.eject(responseInterceptor);
};
}, [apiClient]);
async function login(email, password, callback) {
try {
const reqData = { email, password };
await apiClient.post("/auth/sign_in", reqData);
callback();
} catch (e) {
console.error(e);
throw e;
}
}
function isLoggedIn() {
return accessTokenRef.current === "";
}
const context = {
login,
isLoggedIn,
get: apiClient.get,
post: apiClient.post,
};
return (
<AuthContext.Provider value={context}>
{props.children}
</AuthContext.Provider>
);
}
Run Code Online (Sandbox Code Playgroud)
仪表板
请注意,这loader是一个柯里化函数,例如,一个使用单个参数并返回另一个函数的函数。这是为了在回调作用域中消耗并关闭apiClient.
export const loader = (apiClient) => ({ params, request }) {
// Use passed apiClient to call the API
// For example:
// const response = await apiClient.get("/my-data");
// return response.data;
}
Run Code Online (Sandbox Code Playgroud)
应用程序.js
import { createBrowserRouter } from "react-router-dom";
import axios from "axios";
import "./App.css";
import Dashboard, { loader as dashboardLoader } from "./dashboard";
import AuthProvider from "./AuthProvider";
function newApiClient() {
return axios.create({
baseURL: "http://localhost:3000",
headers: {
"Content-Type": "application/json",
},
});
}
const apiClient = newApiClient();
export const router = createBrowserRouter([
{
path: "/",
element: (<h1>Welcome</h1>),
},
{
path: "/dashboard",
element: (
<AuthProvider apiClient={apiClient}> // <-- pass apiClient
<Dashboard />
</AuthProvider>
),
loader: dashboardLoader(apiClient), // <-- pass apiClient
},
]);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5744 次 |
| 最近记录: |