Managing global HTTP response errors on Vue/axios using Vuex

I've encountered an issue in my VueJS SPA where a strange limbo state occurs. The application fails to recognize that the JWT token has expired, leading it to still display as if the user is logged in. This typically happens after periods of hibernation.

Despite this confusion, users are able to continue sending requests to the API, only to receive a 401 response indicating their unauthorized status.

I'm looking for a way to implement a universal handler for 401 responses. Essentially, I want to automatically clear all user-related data from Vuex and present the page as if the user were a guest, prompting them with a login form popup, etc. Otherwise, I'd be stuck writing a 401 handler for every single request.

I've tried using response interceptors with axios, and while they function properly, they lack access to Vuex (or Vue).

Whenever I attempt to bring Vuex or Vue into my Axios setup, I run into circular dependencies which ultimately break everything.

Even if I simply throw/return the error, I would still need to handle it separately for each request. So, my question is: How can I dispatch methods on this.$store from within an axios interceptor?

The Axios file includes an export default class API that gets globally added to Vue in main.js:

import api from 'Api/api'
// ...
Vue.prototype.$http = api

I had previously assumed there must be a way to access Vue through $http, given that it's a global instance method. However, it seems my assumption was mistaken?

Code

main.js

// ...
import api from 'Api/api'
// ...
Vue.prototype.$http = api

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App },
  vuetify: new Vuetify(opts),
});

api.js

import Client from './ApiClient'

const apiClient = new Client({ basePath: process.env.VUE_APP_API_URL })

const api = {
  get(url) {
    return apiClient._get(`${basePath}/${url}`)
  },
  post(url, data) {
    return apiClient._post(`${basePath}/${url}`, data)
  },
  // ...
}
export default api

ApiClient.js

const axios = require('axios')

const errorHandler = (error) => {
  if (error.response.status === 401) {
    store.dispatch('user/logout') // here is the problem
  }
  return Promise.reject({ ...error })
}


export default class API {
  constructor(options) {
    this.options = Object.assign({ basePath: '' }, options)
    this.axios = axios.create({ timeout: 60000 })
    this.axios.interceptors.response.use(
      response => response,
      error => errorHandler(error)
    )
  }
  // ...
}

Attempting to import the store in ApiClient.js creates a dependency cycle, possibly due to importing Vue within it?

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import PersistedState from 'vuex-persistedstate'
import CreateMutationsSharer from 'vuex-shared-mutations';
import SecureLS from 'secure-ls';
// import modules

Vue.use(Vuex);
const ls = new SecureLS({ encodingType: 'aes' });

export default new Vuex.Store({
  // options
})

Answer №1

conf
import Axios from 'axios'
import IdentityProxy from './IdentityProxy.js'
import UsuariosProxi from './UsuariosProxi'
import ZonasProxi from './ZonasProxi'

// Setting default headers for axios
Axios.defaults.headers.common.Accept='application/json'

// Interceptors for request and response handling
Axios.interceptors.request.use(
    config => {
        let token = localStorage.getItem('access_token');

        if(token){
            config.headers= {
                'x-access-token': `${token}`
            }
        }
        return config;
    },
    error => Promise.reject(error)
);
Axios.interceptors.response.use(
    response => response,
    error => {
      if (error.response.status===403||error.response.status===401) {
        localStorage.removeItem('access_token');
        window.location.reload(true);
      }
   
      return Promise.reject(error);
    }
);

let url=null

if(localStorage.getItem("config")!==null){
    let config = JSON.parse(localStorage.getItem("config"))
    url = config
}

console.log(url)

// Exporting instances of APIs
export default{
    identityProxy: new IdentityProxy(Axios, url),
    _usuarioProxi: new UsuariosProxi(Axios, url),
    _zonasProxi: new ZonasProxi(Axios, url),
}

// Class for handling identity related API calls
export default class IdentityProxy{

    constructor(axios,url){
    this.axios = axios;
    this.url =url;
    }

    register(params){
        return this.axios.post(this.url+'/identity/register',params)
    }

    login(params){
        
        return this.axios.post(this.url+'/auth/signin',params)
    }
}

// Class for handling user related API calls
export default class UsuariosProxi{
    constructor(axios,url){
    this.axios = axios;
    this.url =url;
    }

    getAll(page, take) {
        return this.axios.get(this.url + `/users?page=${page}&take=${take}`);
    }
    
    create(params) {
        return this.axios.post(this.url + '/auth/signup', params);
    }

    get(id) {
        return this.axios.get(this.url + `/users/${id}`);
    }
    
    update(id, params) {
        return this.axios.put(this.url + `/users/${id}`, params);
    }

    remove(id) {
        return this.axios.delete(this.url + `/users/${id}`);
    }
    
    getRoles() {
        return this.axios.get(this.url + '/users/newrol');
    }
}

// Initializing Vuex store for managing application state
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const state = {
    user:null
}
export default new Vuex.Store({
    state
});

Answer №2

main.js File Configuration:

import store from './store';

const AppInstance = new Vue({
  store,
  ...
})

export const { $store } = AppInstance;

By importing { $store } from '@/main.js', you have access to the same instance used in your application, rather than creating a new instance with new Vuex.Store({}) each time.

This method allows for easy exporting of other important instances like $http, $bus, and $t for use in services, tests, helpers, etc...

export const { $store, $http, $bus, $t } = AppInstance;

Answer №3

How about integrating your store directly into ApiClient.js? Here is a possible implementation:

const axios = require('axios')
import store from 'path/to/store'

const errorHandler = (error) => {
if (error.response.status === 401) {
  store.dispatch('user/logout') // access the store
}
  return Promise.reject({ ...error })
}

export default class API {
  constructor(options) {
    this.options = Object.assign({ basePath: '' }, options)
    this.axios = axios.create({ timeout: 60000 })
    this.axios.interceptors.response.use(
      response => response,
      error => errorHandler(error)
    )
  }
  // other methods and functionalities can be added here
}

Answer №4

After delving into discussions on this thread, I successfully devised a solution tailored to my requirements:

main.js

import api, {apiConfig} from 'Api/api'
apiConfig({ store: $store });

ApiClient.js

let configs = {
  store: undefined,
};
const apiConfig = ({ store }) => {
  configs = { ...configs, store };
};
export default api;
export { apiConfig };

By implementing this approach, the api.js file can now be configured and easily expanded in the future.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Rails 4 - Functional JS errors are absent yet JavaScript functionality is not operational

Currently, I am delving into the world of Ruby on Rails and making an effort to grasp the concept of the asset pipeline. As a means to learn more effectively, I decided to construct my website using Rails and learn along the way. However, after integrating ...

Unable to access the computed attribute (array)

My goal is to create a computed property called 'displayImages' that returns an array. The default value of the 'selected' property is set to 0. This property changes based on mouseover and click events. However, when I try to access t ...

Error with tsConfig file not resolving the paths starting with "@/*"

When attempting to create a path alias for "./src/components" that functions in Vite but not in the tsConfig file, I have exhausted all solutions found on Stackoverflow with no success. tsConfig.js { "compilerOptions": { "target" ...

changing the color of arrows in Vue Slick Carousel: a simple guide

I am currently using the vue-slick-carousel plugin for a carousel feature in my project. I am trying to customize the color of the arrows without modifying the default styles provided by the package itself. However, the code snippet I added to the main C ...

Creating a custom JavaScript clock based on stored database values

I am looking to create an analog clock using JavaScript. I have both working hours and off hours stored in a database. My goal is to display working hours in one color and off hours in another color on the clock. How can I achieve this? The database prov ...

After refreshing, Angular route using html5Mode leads to a 'Page Not Found' error page

I have created several Angular routes as illustrated in the code snippet below. app.config(function($routeProvider, $locationProvider, $provide) { $routeProvider .when('/', { templateUrl: 'home.html', controll ...

Transferring the output of a function to a different state

My current challenge involves sending data from one controller to another within an Ionic application. Despite creating a service for this purpose, I am still unable to share the data successfully. The send() function in MainCtrl is meant to pass the data ...

Steer clear of null validations when handling loaded .obj models in Three.js

In my Angular project, I am utilizing three.js to load a simple .obj file and animate it on the canvas. Everything is functioning properly, but I find myself needing to explicitly check for null in my animate() function to determine if the model has been ...

Right-align SELECT-OPTIONS text

Here are the screenshots of the form I'm currently working on. I am aiming to create a select box in the form where the text within the options is aligned to the right. After an option is selected, the chosen text should be displayed as illustrated i ...

The sonar scanner encountered an error while attempting to parse a file using the espree parser in module mode

While executing sonar-scanner on a node project, I encounter a Failed to parse file issue, as shown below: ERROR: Failed to parse file [file:///home/node-app/somedir/index.js] at line 1: Unexpected token './AddCat' (with espree parser in mod ...

At what point does Math.random() begin to cycle through its values?

After running this simple test in nodejs overnight, I found that Math.random() did not repeat. While I understand that the values will eventually repeat at some point, is there a predictable timeframe for when it's likely to happen? let v = {}; for ( ...

What is the best way to send a parameter to a Javascript .done callback from a .success method?

Is there a way to transfer information from a .success function to a done function? $.ajax({ url: "/Asgard/SetLanguagesElf", success: function () { var amount = 7; }, error: function () { alert("SetLanguagesElf"); }, type: &apo ...

transfer chosen data from a text box to a dropdown menu

Looking for: I am in need of a feature where users can input a movie title in a textbox and upon clicking the "move to selectedlist" button, the entered movie title should be added to a dropdown list that displays all the movies selected by the user. Addit ...

How can I dynamically assign @ViewChild('anchor_name') to a newly updated anchor element in Angular 2+?

Upon receiving an item through a GET request, I set the item_id upon subscription. In the HTML file, I create a div with an anchor id="{{this.item_id}}". However, I encountered the following error: FeedComponent.html:1 ERROR TypeError: Cannot read propert ...

Guide on retrieving data from an axios promise in JavaScript

I am struggling to manage the output of multiple lists retrieved through an axios API call made in JavaScript. I want to know how to effectively log the results and save them for future use, particularly for creating a data visualization. Here is my curre ...

Exploring the Haversine Formula and Geolocation Integration in AngularJS

I am currently developing an application that will organize locations based on either name or distance from the user. Everything is functioning properly except for retrieving the distance. I believe I should be able to obtain the user's coordinates th ...

What is the best way to retrieve the latest files from a Heroku application?

Having recently migrated my Discord Bot to Heroku, I faced a challenge with retrieving an updated file essential for code updates. I attempted using both the Git clone command and the Heroku slugs:download command with no success in obtaining the necessar ...

Separating the logic of identical schemas and implementing multi-tenancy in Node.js using Mongoose

In the system I am developing, there are two key requirements: Each client needs to be completely isolated from one another Clients can have multiple subsidiaries which they should be able to switch between without needing to re-authenticate, while ensuri ...

The content momentarily flashes on the page during loading even though it is not visible, and unfortunately, the ng-cloak directive does not seem to function properly in Firefox

<div ng-show="IsExists" ng-cloak> <span>The value is present</span> </div> After that, I included the following lines in my app.css file However, the initial flickering of the ng-show block persists [ng\:cloak], [ng-cloak], ...

A fresh perspective on incorporating setInterval with external scripts in Angular 7

Incorporating the header and footer of my application from external JavaScript files is essential. The next step involves converting it to HTML format and appending it to the head of an HTML file. private executeScript() { const dynamicScripts = [this.app ...