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
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 <></>
}
I have a simple example where I have a circular dependency issue. What can be done to avoid this importing loop?
store.js
import { fetchApi } from "./api";
class Store {
auth = "zzzzz";
fetch = () => fetchApi();
}
const store = new Store();
export default store;
api.js
import client from "./client";
export const fetchApi = () => client("/test");
client.js
import store from "./store";
const client = (endpoint) => {
const config = {
method: "GET",
headers: {
Authorization: `Bearer ${store.auth}`
}
};
return window.fetch(endpoint, config);
};
export default client;
With this code, I have the following situation:
store is importing api
api is importing client
client is importing store
store -> api -> client -> store
Happy to hear suggestions or see examples on how can I avoid this pattern. 🙏🏻
I am new to VueJS and I have been trying to setup graphql with my vuejs but I can't seem to get it right.
Funny thing is there's no error on my console apart from a
[Vue warn]: A plugin must either be a function or an object with an "install" function. error.
And this only comes up when I instantiate vueapollo to the use method.
Here's my main.js file
import { createApp } from 'vue';
import ElementUI from 'element-plus';
import ApolloClient from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
import App from './App.vue';
import router from './router';
import store from './store';
import './assets/style/theme/index.css';
// HTTP connection to the API
const httpLink = createHttpLink({
// You should use an absolute URL here
uri: 'http://localhost:4999/graphql',
});
// Cache implementation
const cache = new InMemoryCache();
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache,
});
/* The provider holds the Apollo client instances
that can then be used by all the child components. */
// eslint-disable-next-line
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
createApp(App)
.use(ElementUI)
.use(apolloClient)
.use(store)
.use(router)
.mount('#app');
I have a page file where presently, I want to reach the "Hello" endpoint I created earlier
<template>
<div>
<h1>Hello</h1>
<h1>{{ hello }}</h1>
</div>
</template>
<script>
import gql from 'graphql-tag';
export default {
data() {
return {
hello: '',
};
},
apollo: {
// Simple query that will update the 'hello' vue property
hello: gql`
query {
hello
}
`,
},
};
</script>
I also struggled with it a couple of weeks ago.
I don't remember how, but I got it to work.
I post my code here, in case it helps anyone in the future.
The difference between the question author and my code is how I pass the Apollo client to the create-app setup function. I don't know if it is correct, but it works.
PS: I am also using TypeScript on top of everything.
My apollo-client.ts file
import { ApolloError } from '#apollo/client';
import { ApolloClient, InMemoryCache, HttpLink } from '#apollo/client/core';
import { ErrorResponse } from '#apollo/client/link/error';
import { onError } from '#apollo/client/link/error';
import { logErrorMessages } from '#vue/apollo-util';
function getHeaders() {
const headers = {
'content-type': 'application/json',
};
/* const token = window.localStorage.getItem('apollo-token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
} */
return headers;
}
// Create an http link:
const httpLink = new HttpLink({
uri: 'http://localhost:3000/query',
fetch: (uri: RequestInfo, options: RequestInit) => {
options.headers = getHeaders();
return fetch(uri, options);
},
});
const errorLink = onError((error: ErrorResponse | ApolloError) => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error);
}
});
// Create the apollo client
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
connectToDevTools: true,
link: errorLink.concat(httpLink),
});
My main.ts file:
import { apolloClient } from './apollo-client';
import { createApp, provide, h } from 'vue';
import { DefaultApolloClient } from '#vue/apollo-composable';
import { createApolloProvider } from '#vue/apollo-option';
import { loadFonts } from './plugins/webfontloader';
import App from './App.vue';
import router from './router';
import store from './store';
import vuetify from './plugins/vuetify';
// TODO: Auth
// import authPlugin from "./auth/authPlugin"
// Create a provider
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
});
loadFonts();
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient);
},
render: () => h(App),
});
app.use(router).use(store).use(vuetify).use(apolloProvider).mount('#app');
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
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)
});
}