如何正确设置带有 React Context 的 Axios 拦截器? [英] How to setup Axios interceptors with React Context properly?

查看:21
本文介绍了如何正确设置带有 React Context 的 Axios 拦截器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于我想使用 React Context 设置 Axios 拦截器,因此似乎唯一可行的解​​决方案是创建一个拦截器组件,以便使用 useContext 挂钩来访问 Context 状态和分派.

Since I want to setup Axios interceptors with React Context, the only solution that seems viable is creating an Interceptor component in order to use the useContext hook to access Context state and dispatch.

问题是,这会创建一个闭包,并在调用拦截器时将旧数据返回给拦截器.

The problem is, this creates a closure and returns old data to the interceptor when it's being called.

我使用 React/Node 使用 JWT 身份验证,我使用上下文 API 存储访问令牌.

I am using JWT authentication using React/Node and I'm storing access tokens using Context API.

这就是我的拦截器组件现在的样子:

This is how my Interceptor component looks like right now:

import React, { useEffect, useContext } from 'react';
import { Context } from '../../components/Store/Store';
import { useHistory } from 'react-router-dom';
import axios from 'axios';

const ax = axios.create();

const Interceptor = ({ children }) => {
  const [store, dispatch] = useContext(Context);

  const history = useHistory();

  const getRefreshToken = async () => {
    try {
      if (!store.user.token) {
        dispatch({
            type: 'setMain',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
          });

        const { data } = await axios.post('/api/auth/refresh_token', {
          headers: {
            credentials: 'include',
          },
        });

        if (data.user) {
          dispatch({
            type: 'setStore',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
            authenticated: true,
            token: data.accessToken,
            id: data.user.id,
            name: data.user.name,
            email: data.user.email,
            photo: data.user.photo,
            stripeId: data.user.stripeId,
            country: data.user.country,
            messages: {
              items: [],
              count: data.user.messages,
            },
            notifications:
              store.user.notifications.items.length !== data.user.notifications
                ? {
                    ...store.user.notifications,
                    items: [],
                    count: data.user.notifications,
                    hasMore: true,
                    cursor: 0,
                    ceiling: 10,
                  }
                : {
                    ...store.user.notifications,
                    count: data.user.notifications,
                  },
            saved: data.user.saved.reduce(function (object, item) {
              object[item] = true;
              return object;
            }, {}),
            cart: {
              items: data.user.cart.reduce(function (object, item) {
                object[item.artwork] = true;
                return object;
              }, {}),
              count: Object.keys(data.user.cart).length,
            },
          });
        } else {
          dispatch({
            type: 'setMain',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
          });
        }
      }
    } catch (err) {
      dispatch({
        type: 'setMain',
        loading: false,
        error: true,
        auth: store.main.auth,
        brand: store.main.brand,
        theme: store.main.theme,
      });
    }
  };

  const interceptTraffic = () => {
     ax.interceptors.request.use(
        (request) => {
            request.headers.Authorization = store.user.token
              ? `Bearer ${store.user.token}`
              : '';

            return request;
          },
        (error) => {
          return Promise.reject(error);
        }
      );

      ax.interceptors.response.use(
        (response) => {
          return response;
        },
        async (error) => {
          console.log(error);
          if (error.response.status !== 401) {
            return new Promise((resolve, reject) => {
              reject(error);
            });
          }

          if (
            error.config.url === '/api/auth/refresh_token' ||
            error.response.message === 'Forbidden'
          ) {
            const { data } = await ax.post('/api/auth/logout', {
              headers: {
                credentials: 'include',
              },
            });
            dispatch({
              type: 'resetUser',
            });
            history.push('/login');

            return new Promise((resolve, reject) => {
              reject(error);
            });
          }

          const { data } = await axios.post(`/api/auth/refresh_token`, {
            headers: {
              credentials: 'include',
            },
          });

          dispatch({
            type: 'updateUser',
            token: data.accessToken,
            email: data.user.email,
            photo: data.user.photo,
            stripeId: data.user.stripeId,
            country: data.user.country,
            messages: { items: [], count: data.user.messages },
            notifications:
              store.user.notifications.items.length !== data.user.notifications
                ? {
                    ...store.user.notifications,
                    items: [],
                    count: data.user.notifications,
                    hasMore: true,
                    cursor: 0,
                    ceiling: 10,
                  }
                : {
                    ...store.user.notifications,
                    count: data.user.notifications,
                  },
            saved: data.user.saved,
            cart: { items: {}, count: data.user.cart },
          });

          const config = error.config;
          config.headers['Authorization'] = `Bearer ${data.accessToken}`;

          return new Promise((resolve, reject) => {
            axios
              .request(config)
              .then((response) => {
                resolve(response);
              })
              .catch((error) => {
                reject(error);
              });
          });
        }
      );
  };

  useEffect(() => {
    getRefreshToken();
    if (!store.main.loading) interceptTraffic();
  }, []);

  return store.main.loading ? 'Loading...' : children;
}

export { ax };
export default Interceptor;

每次用户刷新网站时都会调用 getRefreshToken 函数以检索访问令牌(如果 cookie 中有刷新令牌).

The getRefreshToken function is called every time a user refreshes the website to retrieve an access token if there is a refresh token in the cookie.

interceptTraffic 函数是问题仍然存在的地方.它包含一个请求拦截器,它将一个带有访问令牌的标头附加到每个请求和一个响应拦截器,用于处理访问令牌过期以便使用刷新令牌获取新的.

The interceptTraffic function is where the issue persists. It consists of a request interceptor which appends a header with the access token to every request and a response interceptor which is used to handle access token expiration in order to fetch a new one using a refresh token.

您会注意到我正在导出 ax(我添加了拦截器的 Axios 的一个实例)但是当它在这个组件外被调用时,它引用了旧的存储数据关闭.

You will notice that I am exporting ax (an instance of Axios where I added interceptors) but when it's being called outside this component, it references old store data due to closure.

这显然不是一个好的解决方案,但这就是为什么我需要帮助组织拦截器同时仍然能够访问上下文数据.

This is obviously not a good solution, but that's why I need help organizing interceptors while still being able to access Context data.

请注意,我将此组件创建为包装器,因为它呈现提供给它的子组件,这是主要的 App 组件.

Note that I created this component as a wrapper since it renders children that are provided to it, which is the main App component.

感谢任何帮助,谢谢.

推荐答案

Common Approach (localStorage)

将 JWT 存储在 localStorage 中是一种常见的做法

Common Approach (localStorage)

It is a common practice to store the JWT in the localStorage with

localStorage.setItem('token', 'your_jwt_eykdfjkdf...');

在登录或页面刷新时,创建一个模块,导出带有附加令牌的 Axios 实例.我们将从 localStorage 获取令牌

on login or page refresh, and make a module that exports an Axios instance with the token attached. We will get the token from localStorage

custom-axios.js

import axios from 'axios';

// axios instance for making requests 
const axiosInstance = axios.create();

// request interceptor for adding token
axiosInstance.interceptors.request.use((config) => {
  // add token to request headers
  config.headers['Authorization'] = localStorage.getItem('token');
  return config;
});

export default axiosInstance;

然后,只需导入我们刚刚创建的 Axios 实例并发出请求即可.

And then, just import the Axios instance we just created and make requests.

import axios from './custom-axios';

axios.get('/url');
axios.post('/url', { message: 'hello' });

另一种方法(当您将令牌存储在状态中时)

如果您将 JWT 存储在 state 中,或者您可以从 state 中获取新令牌,请创建一个模块,该模块导出一个函数,该函数将令牌作为参数并返回一个带有附加令牌的 axios 实例,如下所示:

Another approach (when you've token stored in the state)

If you have your JWT stored in the state or you can grab a fresh token from the state, make a module that exports a function that takes the token as an argument and returns an axios instance with the token attached like this:

custom-axios.js

import axios from 'axios';

const customAxios = (token) => {
  // axios instance for making requests
  const axiosInstance = axios.create();

  // request interceptor for adding token
  axiosInstance.interceptors.request.use((config) => {
    // add token to request headers
    config.headers['Authorization'] = token;
    return config;
  });

  return axiosInstance;
};

export default customAxios;

然后导入我们刚刚创建的函数,从状态中获取令牌,并发出请求:

And then import the function we just created, grab the token from state, and make requests:

import axios from './custom-axios';

// logic to get token from state (it may vary from your approach but the idea is same)
const token = useSelector(token => token);

axios(token).get('/url');
axios(token).post('/url', { message: 'hello' });

这篇关于如何正确设置带有 React Context 的 Axios 拦截器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆