How to redirect from axios interceptor with react Router V4?

徘徊边缘 提交于 2019-12-03 04:22:47

问题


I want to make a redirection in axios interceptors when receiving a 403 error. But how can I access the history outside React components ?

In Navigating Programatically in React-Router v4, it's in the context of a React Component, but here i'm trying in axios context

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

回答1:


I solved that by accessing my Redux Store from outside the Component tree and sending it my same action from the logout button, since my interceptors are created in a separated file and loaded before any Component is loaded.

So, basically, I did the following:

At index.js file:

//....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'));

And at the network-service.js file:

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

  }
};

Last, but not least, I have a HOC (Higher Order Component) that I wrap my protected components where I do the actual redirect when the session is out. That way, when I trigger the action type UNAUTH_USER, it sets my isLogged property at my session reducer to false and therefore this component gets notified and does the redirect for me, at any time.

The file for require-auth.js component:

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

Hope that helps!




回答2:


I solved this task by creating browser history from history (https://github.com/ReactTraining/history) package and passing it into the interceptor function and then calling .push() method from it.

The main file code (part of it):

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

...

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

Interceptor configuration:

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

Also, you should use Router from react-router (https://github.com/ReactTraining/react-router) and pass the same history object as history param.

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

Hope this helps!




回答3:


The best solution I found is to define axios.interceptors inside my main React components and use that to handle errors : ( And with withRouter from Router V4 )

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



回答4:


This seems to work for me

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



回答5:


I am using react-router-dom and it has "history" props which can be used in transition to new route

 history.push('/newRoute')



回答6:


The accepted answer doesnt solve my problem. After spending time in axios and tickets around interceptor not triggering, i found, axios doesnt support decorating interceptor globally like it is described above. for future readers, please keep in mind that, axios has tagged this global interceptor as feature. so maybe we will get it in the future realse. for ref: https://github.com/axios/axios/issues/993.

I do have a single axios instance for all the api call, so i solved defining interceptor in it.




回答7:


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

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

ReactTraining/history



来源:https://stackoverflow.com/questions/43210516/how-to-redirect-from-axios-interceptor-with-react-router-v4

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!