hey guys i am trying to do a request inside my action on the vuex side, and i get this error:
Cannot read property '$http' of undefined
i set my vue-resource this way inside my main.js file:
import Vue from 'vue'
import VueResource from 'vue-resource'
import VueRouter from 'vue-router'
import App from './App.vue'
import {routes} from './routes';
import {store} from './store/store';
import VModal from 'vue-js-modal'
Vue.use(VModal)
Vue.use(VueResource);
Vue.use(VueRouter);
const router = new VueRouter({
routes
});
new Vue({
el: '#app',
store,
router,
render: h => h(App)
})
then on the store:
addStyle(state,newStyleObj) {
console.log(newStyleObj);
var vm = this;
this.$http.post('http://localhost:16339/api/Styles/PostStyle/', newStyleObj)
.then(response => {
state.tableStyles = response.body;
console.log(state.tableStyles)
console.log(response.body)
}, error => {
console.log(error);
});
}
any help?
import axios from 'axios'
import Vue from 'vue'
import Vuex from 'vuex'
const axiosInstance = axios.create({
baseURL: '',
withCredentials: true,
})
Vue.prototype.$axios = axiosInstance
Vuex.Store.prototype.$axios = axiosInstance
This works for me.
Now you can access via this.$axios in Vue and Vuex.
You can access Vue instance from the store using this._vm.
this._vm.$http.post()
Here is a proper explanation of the problem that $http is not accessible within vuex https://stackoverflow.com/a/42571288/6355502
The state can only be altered in mutations. NOT in actions. Just commit a mutation from inside of the action to alter the state.
I tried the same last night and got error messages that forced me to do the async fetching in actions which trigger mutations. You cannot do async operations in mutations and you cannot alter the state in actions, so you have to split the code.
// in actions
addStyle ({ commit, state }, newStyleObj) {
console.log(newStyleObj);
var vm = this;
this.$http.post('http://localhost:16339/api/Styles/PostStyle/', newStyleObj)
.then(response => {
commit("setTableStyles", response.body);
console.log(state.tableStyles)
console.log(response.body)
}, error => {
console.log(error);
});
}
// in mutations
setTableStyles(state, payload){
state.tableStyles = payload; // or state.tableStyles.push(...payload) if tableStyles is an Array
}
Outside vue instance (store in this case) use Vue.http (without the dollar sign), inside instance use this.$http.
You can find more on github.
access to axios with Vue.prototype.$http
login({commit}, loginFormData) {
return new Promise((resolve, reject) => {
commit('auth_request');
Vue.prototype.$http({url: '/user/login', data: loginFormData, method: 'POST'})
.then(resp => {
const token = resp.data.data.token;
const user = resp.data.data.profile;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
Vue.prototype.$http.defaults.headers['Authorization'] = 'Bearer ' + token;
this.state.user = JSON.parse(localStorage.getItem('user')) || '';
this.state.token = localStorage.getItem('token') || '';
commit('auth_success', {token, user});
resolve(resp)
})
.catch(err => {
commit('auth_error');
localStorage.removeItem('token');
localStorage.removeItem('user');
reject(err)
})
})
},
Try Accessing vue Properties by this way this._vm.$yourDesiredPropertyName
For example this._vm.$http etc
It worked for me .
You can access all the properties which are properly registered to vue instance
Related
So, I'm trying to make a request with axios in my main.js file.
I'm using, as shown, vue-router to make this request before each component is loaded. However, I'm not able to get this to work when my webpage is loaded for the first time. I mean, axios request is done after the component is loaded. Then, this is going to fail:
mounted() {
if (Vue.prototype.$user.role == "Owner") {
this.isOwner = true;
this.estancoId = Vue.prototype.$user.estanco;
}
},
It shows me this error on the console log:
[Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'role' of undefined"
found in
---> <Header> at src/components/Header.vue
<App> at src/App.vue
<Root>
I tried to make this request with an async/await, I tried methods mounted(), created(), beforeMount(), beforeCreate() but still it's the same. I'm new to Vue.js, and I am stuck here and don't know what to do.
Edit with the whole files to see the app structure:
main.js
import router from './router'
import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import axios from 'axios'
import Vuex from 'vuex'
// Install BootstrapVue
import 'leaflet/dist/leaflet.css';
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
Vue.use(axios)
Vue.use(Vuex)
Vue.config.productionTip = false
const store = new Vuex.Store({
state: {
user : {}
},
mutations : {
set_user (state,user) {
state.user = user
}
}
})
export default store
/* eslint-disable */
router.beforeEach((to, from, next) => {
if (from.path.indexOf("modificarCatalogo") == -1 && to.path.indexOf("modificarCatalogo") == -1) {
localStorage.removeItem("catalogue");
}
if (localStorage.getItem("token") != null) {
axios
.get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
headers: {
Authorization: "Token " + localStorage.getItem("token")
}
})
.then(response => {
store.commit('set_user', response.data);
console.log("First then")
console.log(store.state.user)
}).catch(function (error) {
// handle error case here
console.log(error);
}).then(function () {
// always executed
console.log("Second then")
next();
});
}else{
next();
}
});
/* eslint-enable */
Vue.use(router)
new Vue({
router,
render: h => h(App),
}).$mount('#app')
It has now Vuex because I tried #ellisdod answer but
App.vue
<template>
<div>
<Header />
<router-view />
<Footer />
</div>
</template>
And, in Header.vue, it is where I make the call to, in this case, Vuex $store, but it is the same. I need it to be done everywhere, so I tried to call the method in App.vue but still no results, it returns an empty object now with the solution of Vuex, but just empty, not with user data.
export default {
name: "Header",
data() {
return {
token: localStorage.getItem("token"),
isOwner: "",
estancoId: ""
};
},
mounted() {
console.log("Header log")
if (this.$store.state.user.role == "Owner") {
this.isOwner = true;
this.estancoId = this.$store.state.user.estanco;
}
},
The rest of the components are irrelevant I think
If you use Vuex to store your user data you can prefill the user value with an empty object so it won't throw an error.
const store = new Vuex.Store({
state: {
user : {}
},
mutations : {
set_user (state,user) {
state.user = user
}
},
actions : {
loadUserFromLocal ({commit,state}) {
if (localStorage.getItem("token") === null) return null
return axios
.get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
headers: {
Authorization: "Token " + localStorage.getItem("token")
}
})
.then(response => {
commit('set_user', response.data);
console.log("First then")
console.log(state.user)
}).catch(function (error) {
// handle error case here
console.log(error);
})
}
}
})
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
Then in your mounted hook of your main App component add:
mounted () {
this.$store.dispatch('loadUserFromLocal')
}
Now in your router rather than making a server request before every route, you just check the store:
if (this.$store.state.user.role) {
// handle logged in user
}
Hi,
Complete Answer based on Question Edit,Comments and Answers :
Problem
Vue-router's beforeEach method will only execute in components that are defined in the routes.
In your case,
beforeEach will not be called in Header component as it is not part of routing. It is a standalone component.
Therefore you cannot access $user inside it.
store.js
import axios from 'axios'
import Vuex from 'vuex'
const store = new Vuex.Store({
state: {
user : {}
},
mutations : {
set_user (state,user) {
state.user = user
}
}
actions : {
loadUserFromLocal ({commit,state}) {
if (localStorage.getItem("token") === null) return null
return axios
.get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
headers: {
Authorization: "Token " + localStorage.getItem("token")
}
})
.then(response => {
commit('set_user', response.data);
console.log("First then")
console.log(state.user)
}).catch(function (error) {
// handle error case here
console.log(error);
})
}
}
})
export default store
#ellisdod - thanks.
Now you can use the user variable from store in your component and will be updated when the data is done fetched or will show initial values till that time
Therefore no need for fetching data in router.beforeEach
router.js
// --> all imports
import store from './store' // --> added
// --> all routes to be defined here...
router.beforeEach((to, from, next) => {
// --> other logic for filtering routes
// --> you can use 'store' variable here to get the user data and add
filtering
if (from.path.indexOf("modificarCatalogo") == -1 &&
to.path.indexOf("modificarCatalogo") == -1) {
localStorage.removeItem("catalogue");
}
next();
});
As you know in vue-router, if next is called then navigation is said to be confirmed and the component will be rendered.
Also for more info on using store variable inside router.beforeEach method refer this question
main.js
import router from './router'
import store from './store' // --> added
import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import axios from 'axios'
import Vuex from 'vuex'
// Install BootstrapVue
import 'leaflet/dist/leaflet.css';
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
Vue.use(axios)
Vue.use(Vuex)
Vue.config.productionTip = false
Vue.use(router)
new Vue({
router,
store // --> added
render: h => h(App),
}).$mount('#app')
App.vue
mounted () {
this.$store.dispatch('loadUserFromLocal')
}
#ellisdod - thanks.
Header.vue
export default {
name: "Header",
data() {
return {
token: localStorage.getItem("token")
};
},
computed: {
isOwner() {
return this.$store.state.user.role == "Owner"
}
estancoId () {
return this.$store.state.user.estanco;
}
}
mounted() {
console.log("Header log")
},
}
Using NativeScript vue. I have put axios in its own file, where I can add interceptors etc (in this case to handle a JWT refresh token). In my vuex store (ccStore) I have stored the authToken, and refreshToken. I'm using vuex persist.
import axios from 'axios'
// abridged ...
import ccStore from './store';
const DEV_BASE_URL = ccStore.getters.getBaseURL // local ip injected by webpack
const PROD_BASE_URL = ''
axios.defaults.baseURL = (TNS_ENV === 'production') ? PROD_BASE_URL : DEV_BASE_URL
axios.interceptors.request.use( function (config) {
let token = ''
if(config.url.includes('/auth/refresh')) { //use the refresh token for refresh endpoint
token = ccStore.getters.getRefreshToken;
} else { //otherwise use the access token
token = ccStore.getters.getAuthToken;
}
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
function (error) {
console.log(error)
return Promise.reject(error)
}
)
axios.interceptors.response.use(
function(response) {
console.log(response)
return response
},
function(error) {
const originalRequest = error.config
if(error.response && error.response.status === 401) {
if (originalRequest.url.includes('/auth/refresh')) { //the original request was to refresh so we must log out
return ccStore.dispatch('logout')
} else {
return ccStore.dispatch('refreshToken').then(response => { //try refreshing before logging out
return axios(originalRequest)
})
}
} else {
console.log(error)
return Promise.reject(error)
}
}
)
export default axios;
In my app.js file, I import this modified axios, and assign it to
Vue.prototype.$http = axios;
I did this so that the same instance of axios with interceptors is available in every component [ I ran into some problems doing a refactor last night - circular references when including my modified axios in the mounted hook of each component... but sticking it globally seems to work ]
However, in my app.js file I am also calling ccStore so that I can attach it to my vue instance... Am I doing this right? Is the same instance of ccStore being referenced in both app.js and axios?
Also - to bend the mind further, I have some actions within my vuex store for which I need axios... so I am also having to include axios within my vuex store file - yet axios already includes my vues store...
so...
app.js imports store and axios,
store imports axios,
axios imports store
Is this not circular too?
I don't know if it can be helpful, but I use to initialize a custom Axios instance.
scripts/axios-config.js
import axios from 'axios';
import store from '../store';
import Vue from 'nativescript-vue';
export default {
endpoint: "https://myendpoint.com",
requestInterceptor: config => {
config.headers.Authorization = store.getters.getToken;
return config;
},
init() {
Vue.prototype.$http = axios.create({ baseURL: this.endpoint });
Vue.prototype.$http.interceptors.request.use(this.requestInterceptor);
}
}
app.js
import Vue from 'nativescript-vue';
import store from './store';
import axiosConfig from './scripts/axios-config';
import router from './router'; // custom router
axiosConfig.init();
// code
new Vue({
render: h => h('frame', [h(router['login'])]),
store
}).$start();
No store.js code because I simply import nativescript-vue, vuex and (if needed) axiosConfig object.
I've never had circular problems this way, hope it helps
I have a vue application.
How to access the store from javascript/typescript modules files (import/export)?
for example, I create auth-module that export state, actions, mutations.
export const auth = {
namespaced: true,
state,
actions,
mutations,
getters,
};
In my app I import the module to my store:
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
auth,
}
});
Now, I want to create interceptor (inside my auth-module) for my http calls to add the token from the store.
Vue.http.interceptors.push((request: any) => {
// ---> store.state.token???
// request.headers.set('Authorization', 'Bearer TOKEN');
});
But how can I get access to the state of the store without be depend on my app?
import {store} from './store' but it's okay to import the store instance from vue or vuex module.
You can do that using Plugin.
When you using Plugin, you will get the store instance.
Subscribe to the instance and you get the state, extract token from the state and save it to local variable.
In the Interceptor read this module-global variable.
Here is the solution I built for you:
StoreTokenInterceptorPlugin.ts
import Vue from 'vue';
import VueResource from 'vue-resource';
import { get } from 'lodash';
Vue.use(VueResource);
export const StoreTokenInterceptorPlugin = (store: any) => {
let token: string | null = null;
(Vue.http.interceptors as any).push((request: any) => {
if (token && !request.headers.get('Authorization')) {
request.headers.set('Authorization', `Bearer ${token}`);
}
});
store.subscribe((mutation: any, state: any) => {
token = get(state, 'auth.token') || null;
});
};
in your app store:
import Vue from 'vue';
import Vuex from 'vuex';
import { auth, StoreTokenInterceptorPlugin } from '#modules/auth';
Vue.use(Vuex);
export const store = new Vuex.Store({
state,
modules: {
auth,
} as any,
....
plugins: [StoreTokenInterceptorPlugin],
});
I'm a little bit confused with vuex store component.
How should I obtain state of another module?
I tried a different ways to get data from store and always got Observer object. What is the correct way to knock knock to observer?
If I try to get anything from this object directly, like rootState.user.someVariable then I got undefined response.
Don't have a problem getting state from components.
Edit. Add code
User module
import * as Constants from './../../constants/constants'
import * as types from '../mutation-types'
import axios from 'axios'
const state = { user: [] }
const getters = {
getUser: state => state.user
}
const actions = {
getUserAction ({commit}) {
axios({method: 'GET', 'url': Constants.API_SERVER + 'site/user'})
.then(result => {
let data = result.data
commit(types.GET_USER, {data})
}, error => {
commit(types.GET_USER, {})
console.log(error.toString())
})
}
}
const mutations = {
[types.GET_USER] (state, {data}) {
state.user = data
}
}
export default { state, getters, actions, mutations }
Mutatinos
export const GET_LANGS = 'GET_LANGS'
export const GET_USER = 'GET_USER'
Store
import Vuex from 'vuex'
import Vue from 'vue'
import user from './modules/user'
import lang from './modules/lang'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user,
lang
}
})
Main app
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
Lang module, here is the place where I'm trying get store
import * as types from '../mutation-types'
import {axiosget} from '../../api/api'
const state = { langList: [] }
const getters = {
getLangs: state => state.langList
}
const actions = {
// this two action give me similar result
getLangsAction (context) {
axiosget('lang') // described below
},
getAnotherLangsAction (context) {
console.log(context.rootState.user) <----get Observer object
}
}
const mutations = {
[types.GET_LANGS] (state, {data}) {
state.langList = data
}
}
export default { state, getters, actions, mutations }
axiosget action, api module
import * as Constants from './../constants/constants'
import store from '../store/index'
import axios from 'axios'
export const axiosget = function (apiUrl, actionSuccess, actionError) {
console.debug(store.state.user) // <----get Observer object, previously described
// should append user token to axios url, located at store.state.user.access_token.token
axios({method: 'GET', 'url': Constants.API_URL + apiUrl
+ '?access_token=' + store.state.user.access_token.token})
.then(result => {
let data = result.data
// todo implement this
// }
}, error => {
if (actionError && actionError === 'function') {
// implement this
}
})
}
Component, that call dispatcher. If i get state via mapGetters in computed properties - there is no problems
<template>
<div>
{{user.access_token.token}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ArticlesList',
computed: mapGetters({
user: 'getUser'
}),
created () {
this.$store.dispatch('getLangsAction')
this.$store.dispatch('getAnotherLangsAction')
}
}
</script>
What I'm trying to do in this code - get user access token in main site (after login) and all further manipulations with data will be produced via api host.
Let's say you want to fetch state an attribute userId from object userDetails in Vuex store module user.js.
userDetails:{
userId: 1,
username: "Anything"
}
You can access it in following way in action
authenticateUser(vuexContext, details) {
userId = vuexContext.rootState.user.userDetails.userId;
}
Note: After rootState and before file name user, add the path to the store module file if it is inside nested folders.
I am using VueJS in conjunction with vuex and vue-router. I have a vuex module that is making a mutation to its store, and trying to use that to determine whether or not a user is authenticated.
Here is what my code looks like in relevant part.
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
router.beforeEach((to, from, next) => {
console.log(router.app) // prints a Vue$2 object
console.log(router.app.$store) // undefined
console.log(store.getters.isAuthenticated) // false
...
}
const app = new Vue({
store,
router,
...App
})
app.$mount('#app')
/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import core from './modules/core'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
core: core
}
})
export default store
/store/modules/core.js
import * as types from '../types'
import api from '../../api'
import router from '../../router'
const state = {
token: null,
user: null,
authenticated: false
}
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
console.log('mutate')
state.token = payload.token
state.user = payload.user
state.authenticated = true
router.go('/')
}
}
const getters = {
isAuthenticated: state => {
return state.authenticated
}
}
const actions = {
[types.LOGIN] (context, payload) {
api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
})
}
}
export default {
state,
mutations,
actions,
getters
}
When I go thru my logic to trigger the LOGIN action, I can see that the mutation executed properly, and when I use the Chrome extension to view the vuex state for my core module, the state for user and authenticated have been properly mutated.
QUESTION
It seems like this module just simply has not been loaded by the time the router is running in the .beforeEach loop. Is this true?
If yes, what are some other suggestions on how to handle this situation?
If no, what am I doing incorrect?
console.log(store.state.core.authenticated) return false because you not make a login yet.
In your code you not persist user info in anywhere. E.g. using localstorage
Same considerations:
Not use router.app.$store, use store that you import
In your LOGIN_SUCCESS mutation, store login info and token into localstorage
In your beforeEach hook, check localstorage, if was populated with token, get user information and apply the mutation. If not, just call login page
Something like this..
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
state.token = payload.token
state.user = payload.user
state.authenticated = true
localstorage.setItem('token', payload.token)
localstorage.setItem('user', payload.user)
}
}
const actions = {
[types.LOGIN] (context, payload) {
return api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
return response
})
}
}
router.beforeEach((to, from, next) => {
let user = localstorage.getItem('user')
let token = localstorage.getItem('token')
if (user && token) {
store.commit(types.LOGIN_SUCCESS, {token, user})
next()
}
else if (!store.getters.isAuthenticated) {
store.dispatch(types.LOGIN).then(() => next())
} else {
next()
}
}