I'm having an issue with a linting error in a vue.js project. The error that I get looks like this:
/Users/mikecuddy/Desktop/coding/data_science_projects/statues/client/src/store/modules/common.js
4:1 error Dependency cycle via #/store/index:4 import/no-cycle
I have no idea how to get rid of this error. I tried renaming files, using this.$router and this.$store with no luck. Here is some of my code:
router -> index.js:
The data path is the main one I want to get to. Notice that I have the store import files commented out - that does get rid of the dependency error but then I have issues with doing something like:
this.$store.state.common.loginFlag
as opposed as importing the store and doing this:
store.state.common.loginFlag
import Vue from 'vue';
import VueRouter from 'vue-router';
// import store from '../store/index.js';
// import store from '#/store/index';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/data',
name: 'Data',
component: () => import('../views/Data.vue'),
beforeEnter: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
beforeRouteLeave: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
},
];
const router = new VueRouter({
routes,
});
export default router;
store/modules/common.js:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import router from '../../router';
Vue.use(Vuex);
const data = {
userNotFound: false,
passwordNoMatch: false,
loginFlag: false,
};
const getters = {
userNotFound: (state) => state.userNotFound,
passwordNoMatch: (state) => state.passwordNoMatch,
loginFlag: (state) => state.loginFlag,
};
const actions = {
login: ({ commit }, { payload }) => {
const path = 'http://localhost:5000/login';
axios.post(path, payload)
.then((res) => {
if (res.data.login_flag) {
commit('session/setUserObject', res.data.user, { root: true });
commit('setLoginFlag', res.data.login_flag);
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
}
commit('setNoPasswordMatch', res.data.Password_no_match);
commit('setUserNotFound', res.data.Not_found);
})
.catch((error) => {
console.log(error);
});
},
};
// I have mutations but did not think they'd be needed
const mutations = {};
export default {
namespaced: true,
state: data,
getters,
actions,
mutations,
};
In the common.js file I've tried commenting out:
import router from '../../router';
and that seemed to work - got the Dependency cycle error to go away and in the router/index.js file I was able to get to the route but had an issue with this.$store.state.common.loginFlag when I commented out import store from '#/store/index'; If I leave in the import of: import store from '#/store/index';
then I get the dependency cycle error.
I've also found some help at these other stack pages:
TypeError: Cannot read properties of undefined (reading '$router') vuejs
dependency cycle detected import/no-cycle
I will say that I hate using linters and that's what's giving me the problem here.
Here is the code for store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import common from './modules/common';
import session from './modules/session';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
common,
session,
},
});
Looks like the reason for the dependency cycle here is when you are importing router setup in the store module, and the router in turn imports the whole store. It's okay to use store in router, but try to move routing/redirect logic (these lines):
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
from /modules/common.js to the component or global router hook level, so you avoid router import in the store module.
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")
},
}
I've been following the VueMastery courses and stumbled upon an unexpected problem I cannot seem to resolve myself.
Without using the Global Router Guards the URL updates normally on both modes. But as soon as I add the following hooks to my router (router/index.js) I get no errors whatsoever, but the URL won't update anymore:
router.beforeEach((routeTo, routeFrom, next) => {
NProgress.start();
next();
});
router.afterEach((routeTo, routeFrom, next) => {
NProgress.done();
next();
});
Using:
#vue/cli 4.2.3
vue-router 3.1.5
My complete router (router/index.js) file contains the following script:
import Vue from "vue";
import VueRouter from "vue-router";
import EventList from "../views/EventList.vue";
import store from "#/store/index";
import NProgress from "nprogress";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "event-list",
component: EventList
},
{
path: "/event/create",
name: "event-create",
component: () =>
import(/* webpackChunkName: "event-create" */ "../views/EventCreate.vue")
},
{
path: "/event/:id",
name: "event-show",
component: () =>
import(/* webpackChunkName: "event-show" */ "../views/EventShow"),
props: true,
beforeEnter(routeTo, routeFrom, next) {
store.dispatch("event/fetchEvent", routeTo.params.id).then(event => {
routeTo.params.event = event;
next();
});
}
}
];
const router = new VueRouter({
mode: "history",
routes
});
router.beforeEach((routeTo, routeFrom, next) => {
NProgress.start();
next();
});
router.afterEach((routeTo, routeFrom, next) => {
NProgress.done();
next();
});
export default router;
And is imported and used in my main.js:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
// Automatic Global Component Registraton
import upperFirst from "lodash/upperFirst";
import camelCase from "lodash/camelCase";
// NProgress
import "nprogress/nprogress.css";
const requireComponent = require.context(
// The relative path of the components folder
"./components",
// Whether or not to look in subfolders
false,
// The regular expression used to match base component filenames
/Base[A-Z]\w+\.(vue|js)$/
);
requireComponent.keys().forEach(fileName => {
// Get component config
const componentConfig = requireComponent(fileName);
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(
// Gets the file name regardless of folder depth
fileName
.split("/")
.pop()
.replace(/\.\w+$/, "")
)
);
// Register component globally
Vue.component(
componentName,
// Look for the component options on `.default`, which will
// exist if the component was exported with `export default`,
// otherwise fall back to module's root.
componentConfig.default || componentConfig
);
});
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
What could possibly cause the URL to not update anymore? I tried using the default hash mode but the same problem occurred. The URL's just won't update when clicking the router-links.
There should be an error telling you that
TypeError: next is not a function
because next is not defined in afterEach, since that is the end of the routing middleware chain. If you remove the next from afterEach, it should work again:
router.afterEach((routeTo, routeFrom) => {
NProgress.done();
});
Here is a link to the docs
I have built an app in Vue. It consists of a number of separate modules, each of which corresponds to a route and has a top-level component (and many sub-components/children). Each module has its own store, actions, mutations and getters, as well as API calls dispatched in the created() hooks of components to fetch necessary data.
This is the structure of my app:
Candidates.vue
created() {
this.$store.dispatch('$_candidates/getAllCandidates');
},
/modules/candidates/_store/actions.js
import api from '../_api';
const getAllCandidates = (context) => {
api.fetchAllCandidates
.then((response) => {
context.commit('ALL_CANDIDATES_FETCHED', response.data.candidate_list);
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
};
/modules/candidates/_api/index.js
import { fetchData } from '#/helpers';
const allCandidatesEndpoint =
'https://myapiendpoint.io/candidates/list/all';
const fetchAllCandidates = fetchData(allCandidatesEndpoint, 'get');
export default {
fetchAllCandidates,
};
In the beforeCreate() hook of App.vue, I have a helper function to register all of the application modules in one go. I do this my importing the module stores into the helper file and then registering them. This is my helper file:
helpers.js
import axios from 'axios';
import { store } from '#/store/store';
import candidatesStore from './modules/candidates/_store';
import dashboardStore from './modules/dashboard/_store';
import eventsStore from './modules/events/_store';
import loginStore from './modules/login/_store';
function fetchData(endpoint, requestType, requestBody) {
const apiToken = store.state.apiToken;
delete axios.defaults.auth;
return axios.request({
method: requestType,
data: requestBody,
url: endpoint,
headers: {
'server-token-id': apiToken,
},
})
.then(response => response)
.catch(error => error);
}
/* Register all of the Vuex modules we'll need to manage application state */
function registerVuexModules() {
store.registerModule('$_candidates', candidatesStore);
store.registerModule('$_dashboard', dashboardStore);
store.registerModule('$_events', eventsStore);
store.registerModule('$_login', loginStore);
}
function unregisterVuexModules() {
store.unregisterModule('$_candidates', candidatesStore);
store.unregisterModule('$_dashboard', dashboardStore);
store.unregisterModule('$_events', eventsStore);
store.unregisterModule('$_login', loginStore);
}
export {
fetchData,
registerVuexModules,
unregisterVuexModules,
};
...and I import into App.vue like this:
beforeCreate() {
registerVuexModules();
},
However, the import of each module somehow triggers an API call (using the fetchData function), which returns a 401. I have confirmed this by commenting out various parts of helpers.js - and it is definitely the import rather than the functions themselves.
When I remove the import of a module store into helpers.js, the API call is not attempted for that module's top-level component. The weird parts for me are:
Even though the actions that should trigger these API call are only dispatched in the top level components of each module, the API calls are attempted every time I reload the login page, even before these components are created;
Vue-dev-tools does not register the corresponding events for the actions being dispatched;
If I remove all of the store imports from the helper file, no API calls happen.
I have tried changing the format of my imports in vue-router so that components are lazy-loaded, as I thought this might be the issue. The bundle size decreased but it did not fix the phantom API calls. This is how I import them...
/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import axios from 'axios';
import { store } from '../store/store';
/* Lazy load all of the components required for the routes */
const Login = () => import(/* webpackChunkName: "login" */
'#/modules/login/Login');
const Dashboard = () => import(/* webpackChunkName: "dashboard" */
'#/modules/dashboard/Dashboard');
...
const router = new Router({
routes: [
{
path: '/',
name: 'root',
component: Login,
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
guard(to, from, next);
},
},
...
Can anyone explain this behaviour or what I've missed?
From what I can tell, you have this line
const fetchAllCandidates = fetchData(allCandidatesEndpoint, 'get');
This means that every time you import, it is running the fetchData function and returning the results.
Maybe you meant to do this instead.
const fetchAllCandidates = function ()
{
return fetchData(allCandidatesEndpoint, 'get');
}
Why am I getting Vue is not defined as an error here:
export default {
state: {
projects: {
loading: true,
failed: false,
lookups: [],
selectedId: 0
}
},
mutations: {
loadingProjectLookups (state, payload) {
state.projects.loading = true;
state.projects.failed = false;
}
},
actions: {
loadProjectLookups (context) {
return new Promise((resolve) => {
// VUE NOT DEFINED HERE:
Vue.http.get('https://my-domain.com/api/projects').then((response) => {
context.commit('updateProjectLookups', response.data);
resolve();
},
response => {
context.commit('failedProjectLookups');
resolve();
});
});
}
}
}
This is my vue config:
'use strict';
import Vue from 'vue';
import Vuex from 'vuex';
var VueResource = require('vue-resource');
/* plugins */
Vue.use(Vuex);
Vue.use(VueResource);
/* stores */
import importPageStore from './apps/import/import-page-store';
/* risk notification import */
import ImportApp from './apps/import/import-app.vue';
if (document.querySelector("#import-app")) {
var store = new Vuex.Store(importPageStore);
new Vue({
el: '#import-app',
store,
render: h => h(ImportApp)
});
}
My understanding is that Vue is defined globally and I cannot see why it is not defined. If I add import Vue from 'vue' to my store then I get a message that http is not defined. So I need to work out why Vue appears not to be available globally as I shouldn't have to do this.
I am using webpack to build my vue components. I have other pages rendered using this methodology and they work just fine. But this one does not? I am honestly stumped as to why as I cannot see any differences. The page renders and works. I can see that Vue is working. How can it be undefined?
In a component, you can use this.$http, however, in your store you will need to import Vue every time.
What you can do, is create a service folder and import Vue there. Then just reference your service in the store file.
There's an example here https://github.com/vuejs/vuex/issues/85
Which suggests something like this:
/services/auth.js
import Vue from 'vue'
export default {
authenticate(request) {
return Vue.http.post('auth/authenticate', request)
.then((response) => Promise.resolve(response.data))
.catch((error) => Promise.reject(error));
},
// other methods
}
In your store file:
import { AUTHENTICATE, AUTHENTICATE_FAILURE } from '../mutation-types'
import authService from '../../services/auth'
export const authenticate = (store, request) => {
return authService.authenticate(request)
.then((response) => store.dispatch(AUTHENTICATE, response))
.catch((error) => store.dispatch(AUTHENTICATE_FAILURE, error));
}
// other actions
This is how VueResource extends Vue prototype.
Object.defineProperties(Vue.prototype, {
// [...]
$http: {
get() {
return options(Vue.http, this, this.$options.http);
}
},
// [...]
});
}
VueResource handles the promise itself. Thus, you don't need to wrap the requests in promises. You can use Promise.all() later. But I don't see multiple requests so you just use the get request.
Reference: Using promise in vue-resource
I hope, this would solve your issue with that error.
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.