我有一个基于JWT的API.它在每个响应上轮换令牌.我有一个自定义的提供程序来管理这一点.

我正在try 弄清楚如何在此设置下使用REACT RouteV6.4数据路由.具体地说,我希望使用loader/action函数来获取数据,但这些函数不支持useContext,我不确定如何传递它.

我希望dashboardLoader用当前令牌集作为标头来调用API,AuthContext为我管理这些标头.

目标是让loader函数获取一些数据以显示在仪表板上,并使用来自AuthProviderget()调用.

我目前的替代方案是只在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,
  },
]);

// 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>
  );
}
// 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>;
}

推荐答案

原样创建AXIOS实例,但您将调整授权Provider 以添加请求和响应拦截器来处理令牌和标头.您还将把对apiClient的引用传递给dashboardLoader加载程序函数.

授权Provider

将访问令牌存储在react 引用中,并且直接使用/引用传递的apiClient,而不是将其存储在本地组件状态中(a React anti-pattern).添加useEffect挂钩以添加请求和响应拦截器以维护accessTokenRef值.

export function 授权Provider ({ 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>
  );
}

仪表板

请注意,loader是一个curried function,例如,一个使用单个参数并返回另一个函数的函数.这是在回调作用域中消费和关闭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;
}

App.js

import { createBrowserRouter } from "react-router-dom";
import axios from "axios";
import "./App.css";
import 仪表板, { loader as dashboardLoader } from "./dashboard";
import 授权Provider  from "./授权Provider ";

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: (
      <授权Provider  apiClient={apiClient}> // <-- pass apiClient
        <仪表板 />
      </授权Provider >
    ),
    loader: dashboardLoader(apiClient), // <-- pass apiClient
  },
]);

Javascript相关问答推荐

如何从html元素创建树 struct ?

如何在 cypress 中使用静态嵌套循环

Prisma具有至少一个值的多对多关系

为什么useState触发具有相同值的呈现

使用原型判断对象是否为类的实例

在Matter.js中添加从点A到另一个约束的约束

400 bad request error posting through node-fetch

如何使用JS创建一个明暗功能按钮?

OpenAI转录API错误请求

AddEventListner,按键事件不工作

有没有一种直接的方法可以深度嵌套在一个JavaScript对象中?

对具有相似属性的对象数组进行分组,并使用串连的值获得结果

如何在独立的Angular 应用程序中添加Lucide-Angel?

固定动态、self 调整的优先级队列

在范围数组中查找公共(包含)范围

相对于具有选定类的不同SVG组放置自定义工具提示

Js问题:有没有办法将数据从标记表转换成图表?

为什么我的AJAX联系人表单不能留在同一页上?

呈现其他组件时无法更新组件.要定位错误的setState()调用,请遵循堆栈跟踪,如下所述

UseEffectreact 18-花很长时间进行API调用