Until now, I had been importing axios in each Vue component where I wanted to make HTTP requests, like this.
<script lang="ts">
import axios from 'axios';
#Component
export default class ExamplePage extends Vue {
created(): void {
axios.post(some_path);
}
}
However, now I want to define a global interceptor for all axios requests, basically to catch all 401 unauthorized responses from the backend server (Rails) and log out the user.
My understanding so far is that you must instantiate axios once and use it everywhere, instead of importing and using a different instance in each file.
I've referred to this and this, and tried the below.
// application.js
import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';
Vue.use(VueRouter);
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#application',
router,
render: (h) => h(App),
});
});
axios.interceptors.response.use(
response => response,
error => {
const status = error.response;
if(status === 401) {
// do stuff
}
}
);
Vue.prototype.$http = axios
When I tried to call this.$http.put(...) in another file, it said property $http doesn't exist (I'm guessing it's because this in the context of that component is the component itself, but I'm not sure). How can I fix this?
[UPDATE] Thanks to the responses, I decided to initialize an axios instance in a separate file and then use that instead. However, this is still not working. The 401 responses don't seem to be triggering the interceptor at all.
Is there some additional configuration necessary?
// axios-instance.ts
import axios, { AxiosInstance } from 'axios';
const axiosInstance: AxiosInstance = axios.create();
axiosInstance.interceptors.response.use(
response => response.data,
async function(error) {
console.log("THIS IS THE 401 INTERCEPTOR")
const status = error.response;
if(status === 401) {
// Log out user
}
}
);
export default axiosInstance;
// Some component
<script lang="ts">
import axiosInstance from 'axios-instance';
#Component
export default class ExamplePage extends Vue {
created(): void {
axiosInstance.post(some_path);
}
}
Doesn't solve your question directly, but the way I create instance of axios is:
// axios-instance.js
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
export default instance
After you just import the instance
// application.js
import axios from '../../axios-instance'
You can't use this when you aren't in the vue instance.
Try this:
// application.js
import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';
Vue.use(VueRouter);
axios.interceptors.response.use(
response => response,
error => {
const status = error.response;
if(status === 401) {
// do stuff
}
}
}
);
Vue.prototype.$http = axios;
const VueInstance = new Vue({
el: '#application',
router,
render: (h) => h(App),
});
Now you can make HTTP request using this.$http:
<script>
export default {
methods: {
doStuff() {
this.$http.post('path_to_post_request').then(({ data }) => {
// do something with received data
}).catch((e) => {});
}
}
}
</script>
I had the same problem.
It is not so clear in Axios API docs that error responses should be intercepted in the response interceptor's error callback.
It should be done like this:
axios.interceptors.response.use(function (response) {
//This is the success callback in case u ever need it
return response;
}, function (error) {
// This is the callback I was talking about.
// Do something with request error
// Tip: error.response is the actual response with error code
return Promise.reject(error);
})
This can be seen in the Axios API Documentation - Interceptors section
I recommend that you implement the axios instance in a separate js file:
// utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: '',
headers: {},
withCredentials: true,
// ...other options
})
// request interceptor
service.interceptors.request.use(config => {
// ...
return config
}, err => {
return Promise.reject(err)
})
// response interceptor
service.interceptors.response.use(response => {
// ...
return response
}, err => {
return Promise.reject(err)
})
export default service
And then you can using http request like this:
import request from '#/utils/request'
request({
url: '',
method: 'post',
data: {}
}).then(res => {
// Do something after the request is successful
}).catch(err => {
// Do something after the request fails
})
Try implementing a service:
src / services / yourservice.js
import axios from 'axios'
const apiClient = axios.create({
baseURL: 'https://example.com/api',
withCredentials: false,
timeout: 1000,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('api_username_here' + ':' + 'api_password_here')
}
})
export default {
getOrders(){
return apiClient.get('/orders?status=processing')
},
getProducts(id){
return apiClient.get('/products/' + id)
},
}
Use the service in a Component:
src / component / yourcomponent.vue
import YourService from '#/services/YourService.js';
data() {
return {
orders: null, //Typescript
}
},
created() {
yourservice.getOrder(id).then(response => {
this.orders = response.data
}).catch(error => {
console.log(error)
});
}
Related
I'm trying to implement a locale parameter into my axiosConfig file:
import axios from "axios";
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = "en";
export default instance;
On each screen I make my get and post calls on screens as such:
axiosConfig
.get("/someroute")
.then((response) => {
//console.log(response.data);
})
.catch((error) => {
console.log(error.message);
});
The above code works as intended. Now I want to pass a "locale" parameter into all of my axios calls. This parameter will come from app locale (i use i18next). When I implement it as below, it throws an invalid hook error.
import axios from "axios";
import { useTranslation } from "react-i18next";
const { i18n } = useTranslation();
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = i18n.language;
export default instance;
What would be the correct way to set the locale header in my configuration?
You are getting this error because a hook should be called in a React Component or inside another hook. See Rules of Hooks. And here is what you could do for example:
Transform the file where you are setting the axios instance to a hook:
import axios from "axios";
import { useTranslation } from "react-i18next";
const useAxiosInstance = ()=>{
const { i18n } = useTranslation();
const instance = axios.create({
baseURL: "http://10.0.2.2:8000/api",
timeout: 2000,
});
instance.defaults.headers.common["locale"] = i18n.language;
return instance;
}
export default useAxiosInstance;
You include the hook at the top of the file where you are using the axios config and use it as an example this way:
import {useEffect} from "react";
import useAxiosConfig from "./path";
const AxiosConsumer = ()=>{
const axiosConfig = useAxiosConfig();
useEffect(()=>{
axiosConfig
.get("/someroute")
.then((response) => {
//console.log(response.data);
})
.catch((error) => {
console.log(error.message);
});
},[axiosConfig]);
return <></>
}
The architecture of my app is attached here
Architecture of my app
So in my use case I just want to use getters in Axios interceptor (/../src/app/shared/services/http-client/http-client.js)so I can attach the authorization token in header but when I import store from /../src/app/app-state.js then it throws error as follow
Uncaught TypeError: Cannot read property 'getters' of undefined
at eval (vuex.esm.js?2f62:340)
at Array.forEach (<anonymous>)
at assertRawModule (vuex.esm.js?2f62:339)
at ModuleCollection.register (vuex.esm.js?2f62:244)
at eval (vuex.esm.js?2f62:258)
at eval (vuex.esm.js?2f62:123)
at Array.forEach (<anonymous>)
at forEachValue (vuex.esm.js?2f62:123)
at ModuleCollection.register (vuex.esm.js?2f62:257)
at new ModuleCollection (vuex.esm.js?2f62:218)
app-state.js (vuex store) /../src/app/app-state.js
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from "vuex-persistedstate";
import { appName } from '../environment/environment'
import { authState } from './auth'
import { organizationState } from "./organization"
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
authState,
organizationState
},
strict:false,
plugins: [
createPersistedState ({
key: appName,
})
]
});
main.js
import Vue from 'vue';
import './plugins';
import i18n from './plugins/i18n'
import './plugins/izitoast'
import App from './app/app.vue';
import DEFINES from './plugins/defines'
import './main.scss';
import router from './app/app-routes';
import store from './app/app-state';
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false;
Vue.prototype.DEFINES = DEFINES;
new Vue({
router,
store,
vuetify,
i18n,
render: h => h(App)
}).$mount('#app');
**Organization module state (example of a module) **
import { list } from '../services'
import { getField, updateField } from 'vuex-map-fields';
/** Initial State */
const initialState = {
loading: false,
data: null,
error: null
}
/**
* Organization data mutations
*/
const mutations = {
updateField,
/** Organization data request */
ORGANIZATION_DATA_REQUEST(state,payload) {
Object.assign(state, {loading: true, data: payload})
},
/** Organization data success */
ORGANIZATION_DATA_SUCCESS(state, payload) {
Object.assign(state, {loading: false, data: payload})
},
/** Organization data error */
ORGANIZATION_DATA_ERROR(state) {
Object.assign(state, {
loading: false,
});
},
/** Organization data reset */
ORGANIZATION_DATA_RESET(state) {
Object.assign(state,...initialState)
}
}
const actions = {
async list(context){
// 1. Initiate request
context.commit('ORGANIZATION_DATA_REQUEST');
// 2. Get data from API and handle error
var response = await list().catch(error => {
context.commit('ORGANIZATION_DATA_ERROR')
throw error;
})
// 3. Set data in state
context.commit('ORGANIZATION_DATA_SUCCESS', response)
return response
}
}
const getters ={
getField,
getList: (state) => {
return state.data
},
}
export default {
namespaced: true,
mutations,
actions,
getters,
state: initialState
}
orjanization-state.js which combines all the feature states
import { organizationList } from "./shared/state"
export default {
modules: {
organizationList
}
}
well, if you want to get access to Vuex modules in a js file you can import vuex store and use it,
here is my code you can see I'm using Vuex actions in Axios:
import Store from "#/store/index";
import axios from "axios";
const userRole = localStorage.role ? `${localStorage.role}` : "";
let config = {
baseURL: process.env.VUE_APP_BASE_URL + userRole,
headers: {
Authorization: "Bearer " + localStorage.token
},
};
const _axios = axios.create(config);
// Add a response interceptor
_axios.interceptors.request.use(
function(config) {
Store.dispatch("loader/add", config.url);
return config;
},
function(error) {
Store.dispatch("loader/remove", error.response.config.url);
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function(response) {
// Do something with response data
Store.dispatch("loader/remove", response.config.url);
return response;
},
function(error) {
Store.dispatch("loader/remove", error.response.config.url);
return Promise.reject(error);
}
);
export const $http = _axios;
I hope this code will help you
For vue-axios auth by api_token i use helper file api.js.
i got error - Uncaught TypeError: Cannot read property 'getters' of undefined.
I think api.js helper does not see global storage - Vuex $store.
In other components i do not need import Vuex storage, he avalaible in any place of app.
How use this.$storage in helper?
//api.js
import axios from 'axios'
let api_token = this.$store.getters.get_api_token //got error!
export function get(url) {
return axios({
method: 'GET',
url: url,
headers: {
'Authorization': `Bearer ${api_token}`
}
})
}
//Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
api_token: 'vnbnvnvnvb',
},
getters: {
get_api_token(state){
return state.api_token
}
},
});
export default store
//App.vue
import {get} from './helpers/api';
export default {
created() {
get(`/api/user/${1}`)
.then((res) => {
///do it
})
.catch((err) => {
console.log(err);
})
}
}
Found answer
// some JS file
import store from './../store'; // path to your Vuex store
let user = store.getters.user;
// do stuff with user
https://laracasts.com/discuss/channels/vue/vuex-accessing-vuex-outside-of-a-vue-component
I am using Vue CLI to load an instance property for Axios. I have added a couple of interceptors and I will like the ability to turn them off when using the Axios instance from inside a Vue component.
In the documentation Axios suggests you just label the interceptor then eject is later via the name.
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
So the issue I am having, is that I can't figure out who I can name my interceptor for use in components.
Here is my setup:
api-client.js
import axios from 'axios'
var client = axios.create({
baseURL: process.env.API_DOMAIN,
headers: { Accept: 'application/json' }
})
var unauthorizedInterceptor = client.interceptors.response.use(function (response) {
return response
}, function (error) {
...
return Promise.reject(error)
})
export default client
main.js
import Vue from 'vue'
import apiClient from './api-client'
Vue.prototype.$apiClient = apiClient
new Vue({
el: '#app',
...
})
component.js
<template>
...
</template>
<script>
export default {
data () {
return {
username: null,
email: null,
firstName: null,
lastName: null
}
},
methods: {
updateAccount () {
var self = this
var data = {
username: this.username,
email: this.email,
first_name: this.firstName,
last_name: this.lastName
}
self.$apiClient`enter code here`.interceptors.request.eject(unauthorizedInterceptor) // Remove interceptor here.
self.$apiClient.patch('/me')
.then(function () {
...
})
.catch(function () {
...
})
}
}
}
</script>
How could I name the interceptor to eject it at the component level?
Export the interceptor from your api-client.js and import it where you want to eject it.
api-client.js
export let unauthorizedInterceptor = ...
export default client
component.js
import { unauthorizedInterceptor } from "./api-client"
...
self.$http.interceptors.request.eject(unauthorizedInterceptor)
You may want to export the interceptor function itself as well so that you could add it back if needed.
Alternatively export a function or two that can do this management for you.
api-client.js
let unauthorizedInterceptor
export function removeUnauthorizedInterceptor() {
axios.interceptors.request.eject(unauthorizedInterceptor)
}
export function addUnauthorizedInterceptor(){
unauthorizedInterceptor = axios.interceptors.response.use(...)
}
component.js
import { removeUnauthorizedInterceptor} from "./api-client"
removeUnauthorizedInterceptor()
try set header, in Ajax Request using axios
import React, { Component, PropTypes } from 'react'
import { Link, browserHistory } from 'react-router'
import { connect } from 'react-redux'
import axios from 'axios'
class Income extends Component {
constructor(props) {
super(props)
this.state = {
};
}
componentDidMount() {
axios.post('http://139.196.141.166:8084/course/income/outline', {
headers: { 'X-Authenticated-Userid': '15000500000#1' }
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
<div>
dsdss
</div>
)
}
But server returns me, that I not setup header. I setup it like in documentation, but header is not set.
There screen with error
From what I can see, the method call looks like this (from docs):
axios#post(url[, data[, config]])
As you are posting your config object as second argument, its interpreted as data, not as config (headers is part of the config object)
Edit: Here is an example:
axios.request({
url: 'http://139.196.141.166:8084/course/income/outline',
method: 'post',
headers: { 'X-Authenticated-Userid': '15000500000#1' }
})
Note that i switched over from .post() to .request() which only takes a config object. If you still want to use the .post() call, try:
axios.post(url, {}, { headers: ... })