如何从反应路由器V4的axios拦截器重定向?

时间:2017-04-04 14:45:42

标签: reactjs react-router axios

我想在收到403错误时在axios拦截器中进行重定向。但是,我如何访问React组件之外的历史记录?

Navigating Programatically in React-Router v4中,它位于React组件的上下文中,但是我在这里尝试了axios上下文

axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    if(error.response.status === 403) { console.log("Redirection needed !"); }

    // Trow errr again (may be need for some other catch)
    return Promise.reject(error);
});

10 个答案:

答案 0 :(得分:24)

我通过从组件树外部访问我的Redux Store并从注销按钮向我发送相同的操作来解决这个问题,因为我的拦截器是在一个单独的文件中创建的,并在加载任何Component之前加载。

所以,基本上,我做了以下事情:

index.js档案:

//....lots of imports ommited for brevity
import { createStore, applyMiddleware } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers';
import { UNAUTH_USER } from './actions/types'; //this is just a constants file for action types.

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);

//Here is the guy where I set up the interceptors!
NetworkService.setupInterceptors(store);

//lots of code ommited again...
//Please pay attention to the "RequireAuth" below, we'll talk about it later

ReactDOM.render(
  <Provider store={store}>
      <BrowserRouter>
          <div>
              <Header />
              <main className="plan-container">
                  <Switch>
                      <Route exact path="/" component={Landing} />
                      <Route exact path="/login" component={Login} />
                      <Route exact path="/signup" component={Signup} />
                      <Route exact path="/calendar" component={RequireAuth(Calendar)} />
                      <Route exact path="/profile" component={RequireAuth(Profile)} />
                  </Switch>
              </main>
          </div>
      </BrowserRouter>
  </Provider>
  , document.querySelector('.main-container'));

network-service.js档案:

import axios        from 'axios';
import { UNAUTH_USER } from '../actions/types';

export default {
  setupInterceptors: (store) => {

    // Add a response interceptor
    axios.interceptors.response.use(function (response) {
        return response;
    }, function (error) {
        //catches if the session ended!
        if ( error.response.data.token.KEY == 'ERR_EXPIRED_TOKEN') {
            console.log("EXPIRED TOKEN!");
            localStorage.clear();
            store.dispatch({ type: UNAUTH_USER });
        }
        return Promise.reject(error);
    });

  }
};

最后,但并非最不重要的是,我有一个HOC(高阶组件),我将受保护的组件包装在会话结束时进行实际重定向。这样,当我触发操作类型UNAUTH_USER时,它会将我的isLogged减速器的session属性设置为false,因此会随时通知此组件并为我重定向

require-auth.js组件的文件:

import React, { Component } from 'react';
import { connect } from 'react-redux';

export default function(ComposedComponent) {

    class RequireAuth extends Component {

        componentWillMount() {
            if(!this.props.session.isLogged) {
                this.props.history.push('/login');
            }
        };

        componentWillUpdate(nextProps) {
            if(!nextProps.session.isLogged) {
                this.props.history.push('/login');
            }
        };

        render() {
            return <ComposedComponent {...this.props} />
        }
    }

    function mapStateToProps(state) {
        return { session: state.session };
    }

    return connect(mapStateToProps)(RequireAuth);
}

希望有所帮助!

答案 1 :(得分:13)

我通过从historyhttps://github.com/ReactTraining/history)包创建浏览器历史记录并将其传递给拦截器函数然后从中调用.push()方法来解决此任务。

主文件代码(部分内容):

// app.js
import createHistory from 'history/createBrowserHistory';
import httpService from './api_client/interceptors';

...

const history = createHistory();
httpService.setupInterceptors(store, history);

拦截器配置:

import axios from 'axios';

export default {
  setupInterceptors: (store, history) => {

      axios.interceptors.response.use(response => {
        return response;
      }, error => {

      if (error.response.status === 401) {
        store.dispatch(logoutUser());
      }

      if (error.response.status === 404) {
         history.push('/not-found');
      }

      return Promise.reject(error);
    });
  },
};

此外,您应使用Routerhttps://github.com/ReactTraining/react-router)中的react-router并将同一历史记录对象作为history参数传递。

// app.js
...
ReactDOM.render(
  <Provider store={store}>
     <Router history={history}>
        ...
     </Router>
  </Provider>
, document.getElementById('#root'))

希望这有帮助!

答案 2 :(得分:3)

我找到的最佳解决方案是在我的主要React组件中定义axios.interceptors并使用that来处理错误: (以及来自路由器V4的withRouter

import {withRouter} from 'react-router-dom';

class Homepage extends Component {
  static propTypes = {
    history: PropTypes.object.isRequired
  }

  constructor(props){
    super(props);

    let that = this;
    axios.interceptors.response.use(function (response) {
        // Do something with response data
        return response;
      }, function (error) {
        // Do something with response error
        if(error.response.status === 403) { that.handle403() }

        // Trow errr again (may be need for some other catch)
        return Promise.reject(error);
    });

  }

  handle403(){
    this.props.history.push('/login');
  }

答案 3 :(得分:2)

这似乎对我有用

 function (error) {
            var accessDenied = error.toString().indexOf("401");
            if (accessDenied !== -1) {
              console.log('ACCESS DENIED')
              return window.location.href = '/accessdenied'
            }
          });

答案 4 :(得分:2)

这很好用。

window.location.href = `${process.env.REACT_APP_BASE_HREF}/login`;

答案 5 :(得分:0)

我正在使用react-router-dom,它有“历史”道具,可用于过渡到新路线

 history.push('/newRoute')

答案 6 :(得分:0)

接受的答案不能解决我的问题。我发现在axios中花费时间并且拦截器周围的票未触发后,我发现axios不支持像上面描述的那样全局装饰装饰器。对于将来的读者,请记住,axios已将此global interceptor标记为功能。所以也许我们会在将来实现。供参考:https://github.com/axios/axios/issues/993

对于所有api调用,我确实都有一个axios实例,因此我解决了在其中定义拦截器的问题。

答案 7 :(得分:0)

import { createHashHistory } from 'history' // or createBrowserHistory
const history = createHashHistory()

if (response.status === 403) {
  history.push('/login')
}

ReactTraining/history

答案 8 :(得分:0)

刚刚意识到问题是针对 react router v4 的,我已经写了我在 v5 中使用的答案。

我通过将 useHistory()<Router> 内部传递给 axios 拦截器解决了这个问题。

App.js:

// app.js

function App() {
  return (
    <Router>
      <InjectAxiosInterceptors />

      <Route ... />
      <Route ... />
    </Router>
  )
}

注入AxiosInterceptors.js:

import { useEffect } from "react"
import { useHistory } from "react-router-dom"
import { setupInterceptors } from "./plugins/http"

function InjectAxiosInterceptors () {
  const history = useHistory()

  useEffect(() => {
    console.log('this effect is called once')
    setupInterceptors(history)
  }, [history])

  // not rendering anything
  return null
}

插件/http.js:

import axios from "axios";

const http = axios.create({
  baseURL: 'https://url'
})

/**
 * @param {import('history').History} history - from useHistory() hook
 */
export const setupInterceptors = history => {
  http.interceptors.response.use(res => {
    // success
    return res
  }, err => {
    const { status } = err.response
  
    if (status === 401) {
      // here we have access of the useHistory() from current Router
      history.push('/login')
    }
  
    return Promise.reject(err)
  })
}

export default http

答案 9 :(得分:0)

这是对我有用的已接受答案的修改版本。

使用 BrowserRouter 将 App 组件包装在 index.js 中,否则 useHistory() 钩子将无法工作。

import React from 'react';
...
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter><App /></BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

创建一个单独的文件来实例化自定义 axios 实例

import axios from 'axios';

let headers = {};
const baseURL = "http://localhost:8080"
const jwtToken = localStorage.getItem("Authorization");

if (jwtToken) {
    headers.Authorization = 'Bearer ' + jwtToken;
}

const axiosInstance = axios.create({
    baseURL: baseURL,
    headers,
});

export default axiosInstance;

使用拦截器方法为之前创建的自定义 axios 实例创建另一个文件。

import axiosInstance from "./ServerAxios";
import { useHistory } from "react-router-dom";

const baseURL = "http://localhost:8080"

const SetupInterceptors = () => {
    let history = useHistory();
    axiosInstance.interceptors.response.use(function (response) {
        return response;
    }, function (error) {
        var status = error.response.status;
        var resBaseURL = error.response.config.baseURL;
        if (resBaseURL === baseURL && status === 403) {
            localStorage.removeItem("Authorization");
            history.push("/login");
        }
        return Promise.reject(error);
    });
}

export default SetupInterceptors;

然后导入并调用App.js文件中的setup方法

...
import { createBrowserHistory } from 'history';
import SetupInterceptors from './middleware/NetworkService';
const App = () => {
  const history = createBrowserHistory();
  SetupInterceptors(history);
...

然后每当需要使用自定义 axios 实例时,导入实例化文件并使用它。

import ServerAxios from "../middleware/ServerAxios";
ServerAxios.post(......);