Search code examples
react-nativehttp-redirectaxiosreact-router-native

Redirect to View Component from an Axios interceptor (outside of a component) in React Native with React-Router-Native


I am working on a service class to create an instance of Axios (in React Native) where I'm using some interceptors to inject the JWT Token to authenticate against an API.

When the app detects that the access token is expired, automatically use the refresh token to request for a new token and a new refresh token at the same time.

The problem is when the refresh token become expired due to inactivity.

I want to redirect the user to the Login view component when the refresh token become expired.

What do I need to do to redirect to my Login view component taking into account that the service class is stateless and doesn't care what component it is called from?

This is what I have until now:


//API.js

import axios from 'axios';
import TokenStorage from './TokenStorage';

const API = axios.create({
    responseType: 'json',
});

...

API.interceptors.response.use((response) => {
    // Return a successful response back to the calling service
    return response;
}, (error) => {

    // Return any error which is not due to authentication back to the calling service
    ...

    // Logout user if token refresh didn't work or user is disabled
    if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {

        TokenStorage.cleanStorage();

        // TODO - Redirect to the "/login" route [Login View Component]

        return new Promise((resolve, reject) => {
            reject(error);
        });
    } else if (error.config.url === '/tokens/') {
        // Reject if user credentials are wrong
        return new Promise((resolve, reject) => {
            reject(error);
        });
    }

    // Try request again with new token
    ...

});

export default API;

Environment:

"axios": "^0.19.0",
"react": "16.9.0",
"react-native": "0.61.2",
"react-router-native": "^5.1.2"

PS: This is a very similar question to this, but I need a solution using React Router Native.

EDIT 1:

I already tried with the "history": "^4.10.1" package as follow, with no success:


// API.js

import {createMemoryHistory} from 'history';

const history = createMemoryHistory();

...
// Logout user if token refresh didn't work or user is disabled
    if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {

        TokenStorage.cleanStorage();

        history.push('/login'); // This don't work!

        return new Promise((resolve, reject) => {
            reject(error);
        });
    } else if (error.config.url === '/tokens/') {
        // Reject if user credentials are wrong
        return new Promise((resolve, reject) => {
            reject(error);
        });
    }


Solution

  • Without Redux

    //EventPublisher.js    
    export default class EventPublisher {
      static instance;
      listeners = {};
    
      constructor(validEvents){
        validEvents.forEach(ve=>{
          this.listeners[ve] = []
        })
      }
    
      subscribe(event, listener){
        if(!this.listeners[event]) throw 'Invalid event'
        this.listeners[event].push(listener)
        return ()=>{
          this.listeners[event].remove(listener)
        }
      }
    
      publish(event){
        this.listeners[event].forEach(l=>l())
      }
    }
    
    

    In your top level component

    componentDidMount(){
      EventPublisher.instance = new EventPublisher([
        'REFRESH_TOKEN',
      ])
      this.cancelEventSubscription = EventPublisher.instance.subscribe('REFRESH_TOKEN', ()=>{
        //Logic of react router native to navigate to LoginScreen
      })
    }
    componentWillUnmount(){
      this.cancelEventSubscription()
    }
    

    In axios

    if (error.config.url === '/tokens/refresh/' || error.response.data.code === 'access_invalid') {
    
            TokenStorage.cleanStorage();
    
            // TODO - Redirect to the "/login" route [Login View Component]
            EventPublisher.instance.publish('REFRESH_TOKEN')
    
            return new Promise((resolve, reject) => {
                reject(error);
            });
        } else if (error.config.url === '/tokens/') {
            // Reject if user credentials are wrong
            return new Promise((resolve, reject) => {
                reject(error);
            });
        }