Property or method "navigationLinks" is not defined on the instance but referenced during render. Make sure that this property is reactive - vuex

Am stack in calling data from vuex module to component.
Am calling Navigation links where I have created a module navigation.js. I have imported the module in the main store.
Am using the getters to call the data in the component.
My navigation component looks like this
<template>
<nav>
<ul>
<li v-for="link in navigationLinks" :key="link.label">
<router-link :to="link.route">{{ link.label }}</router-link>
</li>
</ul>
</nav>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import store from '#/store';
import navigation from '#/store/navigation';
export default {
name: 'Navbar',
data: {
},
computed: {
...mapGetters(['navigationLinks'])
}
}
In my main store.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './auth'
import navigation from './navigation'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
auth,
navigation
}
})
In my navigation.js I have
const state = {
links: [
{ label: 'Home', route: '/', icon: 'home' },
{ label: 'About', route: '/about', icon: 'info' },
{ label: 'Contact', route: '/contact', icon: 'envelope' },
]
}
const getters = {
navigationLinks: state => state.links
}
export default {
state,
getters
}

Related

Uncaught Error: [vuex] getters should be function but "getters.products" in module "prods" is []

I've reduced this code to the bare minimal, but I'm still not sure where this problem is coming from, but this is the first sentence of the error:
Uncaught Error: [vuex] getters should be function but "getters.products" in module "prods" is []
This is my main.js:
import { createApp } from 'vue';
import router from './router.js';
import storage from './store.js';
import App from './App.vue';
const app = createApp(App);
app.use(router);
app.use(storage);
app.mount('#app');
This is my router.js:
import { createRouter, createWebHistory } from 'vue-router';
import ProductsList from './ProductsList.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', redirect: '/products' },
{ path: '/products', component: ProductsList },
]
});
export default router;
This is my store.js:
import { createStore } from 'vuex';
// import productsModule from './products.js';
const products = createStore({
namespaced: true,
state() {
return {
allProducts: [
{
id: 'p1',
image: "",
title: 'Books',
description: 'Books collection.',
price: 20
},
]
};
},
getters: {
products(state) {
return state.allProducts;
}
}
})
const store = createStore({
modules: {
prods: products,
},
});
export default store;
This is my App.vue minus the style:
<template>
<the-header></the-header>
<router-view></router-view>
</template>
<script>
import TheHeader from './TheHeader.vue';
export default {
components: {
TheHeader
},
}
</script>
This is my ProductsList.vue minus the style:
<template>
<section>
<ul>
<product-item
v-for="prod in products"
:key="prod.id"
:id="prod.id"
:title="prod.title"
:image="prod.image"
:description="prod.description"
:price="prod.price"
></product-item>
</ul>
</section>
</template>
<script>
import ProductItem from './ProductItem.vue';
export default {
// inject: ['products'],
components: {
ProductItem,
},
computed: {
products() {
return this.$store.getters['prods/products'];
}
}
};
</script>
And this is my ProductItem.vue minus the style:
<template>
<li class="product">
<div class="product__data">
<div class="product__image">
<img :src="image" :alt="title" />
</div>
<div class="product__text">
<h3>{{ title }}</h3>
<h4>${{ price }}</h4>
<p>{{ description }}</p>
</div>
</div>
<div class="product__actions">
<button>Add to Cart</button>
</div>
</li>
</template>
<script>
export default {
props: ['id', 'image', 'title', 'price', 'description'],
};
</script>
My code including the styles and the workable products.js can be found at:
https://github.com/maxloosmu/vue-complete/tree/main/15/vuex-0012/src
Could someone help point me to why this way of using Vuex getters is wrong? Or is it due to another problem with Vuex?
I've discovered an answer to my question. In my store.js, I do not use createStore twice, but just once. That will resolve the problem:
import { createStore } from 'vuex';
const products = {
namespaced: true,
state() {
return {
allProducts: [
{
id: 'p1',
image: "",
title: 'Books',
description: 'Books collection.',
price: 20
},
]
};
},
getters: {
products(state) {
return state.allProducts;
}
}
}
const store = createStore({
modules: {
prods: products,
},
});
export default store;

Vue3 with vuetify alpha

I am trying to install the latest version of Vuetify 3 with Vue 3 but I am getting an error at run time. I am upgrading from Vue2 to Vue3. It doesn't appear I am integrating Vuetify correctly. I have spent all labor day trying to figure this one out.
Uncaught TypeError: Cannot read properties of undefined (reading 'lgAndUp')
It's coming from my Vue component:
<template>
<div
:class="{
'mx-5 px-5 pt-5': $vuetify.breakpoint.lgAndUp,
'py-5 my-3 px-0': $vuetify.breakpoint.mdAndDown
}"
id="login_router_wrapper"
>
<router-view id="login_router"></router-view>
</div>
</template>
<script>
import { mapState, mapActions, mapMutations } from 'vuex';
export default {
props: ['errors'],
data() {
return {};
},
async created() {},
destroyed() {},
computed: {},
methods: {}
};
</script>
<style></style>
My main login_app.js
import 'material-design-icons-iconfont/dist/material-design-icons.css';
import 'vuetify/styles' // Global CSS has to be imported
import { createApp } from 'vue';
import store_login from './store/index-login.js';
import router from './router/index-login.js';
import my_vuetify from './vuetify.js';
import AuthRouter from './components/auth/AuthRouter.vue';
import AuthEntrypoint from './components/auth/AuthEntrypoint.vue';
const login_app = createApp({
data() {
return {
message: 'Hello root Component 1'
};
},
components: {
'auth-router': AuthRouter,
'auth-entrypoint': AuthEntrypoint,
},
});
login_app.use(store_login)
.use(router)
.use(my_vuetify)
.mount('#app-login');
My store
import {createStore} from 'vuex';
import auth from './auth/auth.js';
import loader from './util/loader.js';
import error from './util/error.js';
const store_login = createStore({
modules: {
auth,
loader,
error
},
state: {
errors: []
},
mutations: {},
getters: {},
actions: {}
});
export default store_login;
My router
import { createRouter, createWebHistory } from 'vue-router';
import auth from './auth.js';
import AuthRouter from '../components/auth/AuthRouter.vue';
import authResetPassword from './authResetPassword.js';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
props: true,
component: AuthRouter,
children: [...auth]
},
{
path: '/password',
props: true,
component: AuthRouter,
children: [...authResetPassword]
}
]
});
export default router;
Then Vuetify
import {createVuetify} from 'vuetify';
import 'vuetify/styles' // Global CSS has to be imported
const my_vuetify = createVuetify({
iconfont: 'mdi', // 'md' || 'mdi' || 'fa' || 'fa4'
theme: {
options: {
customProperties: true
},
light: true,
themes: {
light: {
primary: '#9E9E9E',
secondary: '#b0bec5',
accent: '#8c9eff',
error: '#b71c1c',
background: '#fafafa'
}
}
}
});
export default my_vuetify;

Vue 3 dynamic components at router level

Dynamic imports is needed for me, eg. i have 10 layouts, but user only visited 3 layouts, I should not import all of the layouts, since its consumed unnecessary resources.
Since its dynamic import, each time i switch between Login & Register path <RouterLink :to"{name: 'Login'}" /> & <RouterLink :to"{name: 'Register'}" />, I got rerender or dynamic import the layout again.
My question is what is the better way to handle it, without rerender or dynamic import the layout again? Or can I save the dynamic import component into the current vue 3 context?
App.vue this is my app with watching the router and switch the layout based on route.meta.layout
<template>
<component :is="layout.component" />
</template>
<script>
import DefaultLayout from "./layout/default.vue";
import {
ref,
shallowRef,
reactive,
shallowReactive,
watch,
defineAsyncComponent,
} from "vue";
import { useRoute } from "vue-router";
export default {
name: "App",
setup(props, context) {
const layout = shallowRef(DefaultLayout);
const route = useRoute();
watch(
() => route.meta,
async (meta) => {
if (meta.layout) {
layout = defineAsyncComponent(() =>
import(`./layout/${meta.layout}.vue`)
);
} else {
layout = DefaultLayout;
}
},
{ immediate: true }
);
return { layout };
},
};
</script>
router/index.js this is my router with layout meta
import { createRouter, createWebHistory } from "vue-router";
import Home from "#/views/Home.vue";
import NotFound from "#/views/NotFound.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/login",
name: "Login",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "login" */ "../views/Login.vue");
},
},
{
path: "/register",
name: "Register",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "register" */ "../views/Register.vue");
},
},
{ path: "/:pathMatch(.*)", component: NotFound },
];
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_GITLAB_BASE_PATH),
routes,
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 };
},
});
export default router;
You could use AsyncComponent inside the components option and just use a computed property that returns the current layout, this will load only the current layout without the other ones :
components: {
layout1: defineAsyncComponent(() => import('./components/Layout1.vue')),
layout2: defineAsyncComponent(() => import('./components/Layout2.vue')),
},
Had this issue and Thorsten Lünborg of the Vue core team helped me out.
add the v-if condition and that should resolve it.
<component v-if="layout.name === $route.meta.layout" :is="layout">

How can I pass data from a component to another component on vue?

I have 2 components
My first component like this :
<template>
...
<b-form-input type="text" class="rounded-0" v-model="keyword"></b-form-input>
<b-btn variant="warning" #click="search"><i class="fa fa-search text-white mr-1"></i>Search</b-btn>
...
</template>
<script>
export default {
data () {
return {
keyword: ''
}
},
methods: {
search() {
this.$root.$emit('keywordEvent', this.keyword)
location.href = '/#/products/products'
}
}
}
</script>
My second component like this :
<template>
...
</template>
<script>
export default {
data () {
return{
keyword: ''
}
},
mounted: function () {
this.$root.$on('keywordEvent', (keyword) => {
this.keyword = keyword
})
this.getItems()
},
methods: {
getItems() {
console.log(this.keyword)
....
}
}
}
</script>
I using emit to pass value between components
I want to pass value of keyword to second component
/#/products/products is second component
I try console.log(this.keyword) in the second component. But there is no result
How can I solve this problem?
Update :
I have index.js which contains vue router like this :
import Vue from 'vue'
import Router from 'vue-router'
...
const Products = () => import('#/views/products/Products')
Vue.use(Router)
export default new Router({
mode: 'hash', // https://router.vuejs.org/api/#mode
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/pages/login',
name: 'Home',
component: DefaultContainer,
children: [
{
path: 'products',
redirect: '/products/sparepart',
name: 'Products',
component: {
render (c) { return c('router-view') }
},
children : [
...
{
path: 'products',
name: 'products',
component: Products,
props:true
}
]
},
]
},
{
path: '/products/products',
name: 'ProductsProducts', // just guessing
component: {
render (c) { return c('router-view') }
},
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
]
})
From this code...
location.href = '/#/products/products'
I'm assuming /#/products/products maps to your "second" component via vue-router, I would define the keyword as a query parameter for the route. For example
{
path: 'products',
name: 'products',
component: Products,
props: (route) => ({keyword: route.query.keyword}) // set keyword query param to prop
}
Then, in your component, define keyword as a string prop (and remove it from data)
props: {
keyword: String
}
and instead of directly setting location.href, use
this.$router.push({name: 'products', query: { keyword: this.keyword }})
There are some ways to do it in Vue.
Use EventBus with $emit like you did;
event-bus.js
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
component1.vue :
import EventBus from './event-bus';
...
methods: {
my() {
this.someData++;
EventBus.$emit('invoked-event', this.someData);
}
}
component2.vue
import EventBus from './event-bus';
...
data(){return {changedValue:""}},
...
mounted(){
EventBus.$on('invoked-event', payLoad => {
this.changedValue = payload
});
}
Use Vuex store, will be accessible at any component, at any page; (my favorite way)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = () =>
new Vuex.Store({
store: {myStore:{value:0}},
actions:{["actionName"]:function({commit}, data){commit("actionName", data);}}, // I usualy using special constant for actions/mutations names, so I can use that here in code, and also in components
mutations:{["actionName"]:function(state, data){state.myStore.value = data;}},
getters:{myStoreValue: state => !!state.myStore.value}
})
component1.vue
...
methods:{
change:function(){
this.$store.dispatch("actionName", this.someData); //nuxt syntax, for simple vue you have to import store from "./../store" also
}
}
component2.vue
...
data(){return {changedValue:""}},
...
mounted(){
this.changedValue = this.$store.getters.myStoreValue;
}
Use props like #Phil said.

Changing a vuex state from a different component?

I have a component (modal) which relies on a store. The store has the state of the modal component - whether it is active or not.
I need to be able to call this modal to open from other components or even just on a standard link. It opens by adding an .active class.
How can I change the state of the store - either by calling the stores action or calling the modal components method (which is mapped to the store).
Modal Store:
class ModalModule {
constructor() {
return {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
};
}
}
export default ModalModule;
Vue Component:
<template>
<div class="modal" v-bind:class="{ active: active }">
<div class="modal-inner">
<h1>modal content here</h1>
</div>
<div class="modal-close" v-on:click="this.toggleActive">
X
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapGetters('Modal', [
'active',
])
},
methods: {
...mapActions('Modal', [
'toggleActive',
]),
}
}
</script>
And somewhere else I want to be able to have something like:
<button v-on:click="how to change the state??">OPEN MODAL</button>
Edit:
Here's the store:
import Vuex from 'vuex';
import ModalModule from './ModalModule';
class Store extends Vuex.Store {
constructor() {
Vue.use(Vuex);
super({
modules: {
Modal: new ModalModule(),
}
});
};
}
You do not need an action for your particular usecase . You just just define a mutation as you are just changing the boolean value of a property in a state. Actions are for async functionality. You usecase is just synchronous change of Boolean value
So you can do
<button v-on:click="$store.commit('toggleActive')">OPEN MODAL</button>
EDIT:
Just export a plain object
const ModalModule = {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
}
export default ModalModule;// export the module
Even remove the class based definition of the store
import Vue from 'vue'
import Vuex from 'vuex';
import ModalModule from './ModalModule';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
ModalModule
}
});
And change it like this in you component for mapping of the mutation (<MODULE_NAME>/<MUTATION_NAME>)
...mapMutations([
'ModalModule/toggleActive'
])
You can access the store from your components via this.$store. There you can call your actions and mutations. So
<button v-on:click="$store.commit('your mutation name', true)">OPEN MODAL</button>