Can anyone help with the below, I am getting the following error Cannot read properties of undefined (reading 'getters')
I am working on a project where my stores should return an array to my index.vue
Is there also any way I can get around this without having to use the Vuex store?
My store directory contains the below files
index.js
export const state = () => ({})
parkingPlaces.js
import {getters} from '../plugins/base'
const state = () => ({
all: []
});
export default {
state,
mutations: {
SET_PARKINGPLACES(state, parkingPlaces) {
state.all = parkingPlaces
}
},
actions: {
async ENSURE({commit}) {
commit('SET_PARKINGPLACES', [
{
"id": 1,
"name": "Chandler Larson",
"post": "37757",
"coordinates": {
"lng": -1.824377,
"lat": 52.488583
},
"total_spots": 0,
"free_spots": 0
},
]
)
}
},
getters: {
...getters
}
}
index.vue
<template>
<div class="min-h-screen relative max-6/6" >
<GMap class="absolute inset-0 h-100% bg-blue-400"
ref="gMap"
language="en"
:cluster="{options: {styles: clusterStyle}}"
:center="{lat:parkingPlaces[0].coordinates.lat, lng: parkingPlaces[0].coordinates.lng}"
:options="{fullscreenControl: false, styles: mapStyle}"
:zoom="5"
>
<GMapMarker
v-for="location in parkingPlaces"
:key="location.id"
:position="{lat: location.coordinates.lat, lng: location.coordinates.lng}"
:options="{icon: location.free_spots > 0 ? pins.spacefree : pins.spacenotfree}"
#click="currentLocation = location"
>
<GMapInfoWindow :options="{maxWidth: 200}">
<code>
lat: {{ location.coordinates.lat }},
lng: {{ location.coordinates.lng }}
</code>
</GMapInfoWindow>
</GMapMarker>
<GMapCircle :options="circleOptions"/>
</GMap>
</div>
</template>
<script>
import {mapGetters, mapActions} from 'vuex';
export default {
// async mounted() {
// // // console.log('http://localhost:8000/api/parkingPlace')
// // console.log(process.env.API_URL)
// // const response = await this.$axios.$get('PARKING_PLACE')
// //
// // console.log('response', response)
//
// // console.log(location)
// },
data() {
return {
currentLocation: {},
circleOptions: {},
// parkingPlaces: [
//array of parkingPlaces
// ],
pins: {
spacefree: "/parkingicongreen3.png",
spacenotfree: "/parkingiconred3.png",
},
mapStyle: [],
clusterStyle: [
{
url: "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m1.png",
width: 56,
height: 56,
textColor: "#fff"
}
]
}
},
computed: {
...mapGetters({
'parkingPlaces': "parkingPlaces/all"
})
},
async fetch() {
await this.ensureParking()
},
methods: {
...mapActions({
ensureParking: 'parkingPlaces/ENSURE'
})
}
}
</script>
base.js
import getters from "./getters";
export {getters};
getters.js
export default {
all: state => state.all
};
Image of my file directory below
image of error
why you need state management :
Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.
today you can try nuxt3 then you have access to Nuxt3 state management
or try Pinia not Vuex
these are better options for Vue3.
if you want to use Vue2, Nuxt2 and Vuex as state management so:
first why your getters file is in plugins?
use this structure:
|__store
⠀⠀|__ index.js
⠀⠀|__ getters.js
your getter file content should be like this :
export default {
//your getters
};
then you can import these getters in your index.js file like this:
import getters from "./getters";
const store = createStore({
state () {
return {
something: 0
}
},
getters
})
and then you use mapGetters:
...mapGetters({
parkingPlaces: 'all'
})
if you have another store you should use modules :
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
you can separate files and the structure will be:
|__ store
⠀⠀|__ index.js # where we assemble modules and export the store
⠀⠀|__ actions.js # root actions
⠀⠀|__mutations.js # root mutations
⠀⠀|__ modules
⠀⠀⠀⠀|__ moduleA.js # moduleA module
⠀⠀⠀⠀|__ moduleB.js # moduleB module
In parkingPlaces.js: Try using import {getters} from '../plugins/base.js' instead of import {getters} from '../plugins/base'.
In base.us: try using import getters from './getters.js' instead of import getters from './getters'.
I believe that you have namespace issue or your store isn't initialized in the right way.
Make sure that you register your parkingPlaces.js module with namespaced property -
import parkingPlacesModule from './parkingPlaces.js';
const store = new Vuex.Store({
modules: {
parkingPlaces: {
namespaced: true,
...parkingPlacesModule
}
}
});
In addition, you can pass the name to the mapGetters helper, like that:
computed: {
...mapGetters('parkingPlaces', [
'all', // -> this.all
])
}
You can try it by below means
In getters.js
export const getters = {
all: state => state.all
};
In base.js
export * from "./getters";
This above way will make your file base.js an object supplying the getters object.
Related
Original question:
vuex shared state in chrome extension
I have the following setup in a chrome extension;
A content script that needs to write to a vuex store
A background script that initializes that store
And a popup script that renders stuff from the store (received from the content script)
store.js
import Vue from "vue";
import Vuex from "vuex";
import "es6-promise/auto";
import createMutationsSharer from "vuex-shared-mutations";
import dummyData from "./dummyData";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
chromePagesState: {
allSections: [],
},
},
mutations: {
setChromePagesState(state, value) {
state.chromePagesState = value;
},
addWhiteListedItem(state, item) {
state.chromePagesState.allSections[0].itemSectionCategory[0].tasks.splice(
0,
0,
item
);
},
},
actions: {
// init from local storage
async loadChromePagesState({ commit }) {
const json = await getStorageValue("inventoryData");
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? dummyData
: JSON.parse(JSON.stringify(json))
);
},
// send message to background script to call init (shortened)
async loadChromePagesStateBrowser({ commit }) {
browser.runtime
.sendMessage({ type: "storeinit", key: "chromePagesState" })
.then(async (chromePagesState) => {
const json = await getStorageValue("inventoryData");
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? dummyData
: JSON.parse(JSON.stringify(json))
);
});
},
},
// stuff from vuex-shared-mutations
plugins: [
createMutationsSharer({
predicate: [
"addWhiteListedItem",
"loadChromePagesState",
"loadChromePagesStateBrowser",
],
}),
],
});
The content script calls store from a vue component:
index.js
import store from "../popup/firstpage/store";
new Vue({
el: overlayContainer,
store,
render: (h) => h(Overlay, { props: { isPopUp: isPopUp } }),
});
Overlay.vue
<script>
import { mapState, mapMutations } from "vuex";
export default {
props: ["isPopUp"],
data() {
return {
};
},
computed: mapState(["chromePagesState"]),
methods: {
...mapMutations(["addWhiteListedItem"]),
// this gets called in the template
addToWhiteList() {
let newItem = initNewItemWithWebPageData();
this.addWhiteListedItem(newItem);
},
},
}
</script>
The background script receives a message and calls a mutation on the store:
background.js
import store from "../content/popup/firstpage/store";
browser.runtime.onMessage.addListener((message, sender) => {
if (message.type === "storeinit") {
store.dispatch("loadChromePagesState");
return Promise.resolve(store.state[message.key]);
}
});
Upon opening popup.js, a store mutation is called that sends a message to background.js that calls another mutation in the store:
popup.js
import store from "./firstpage/store";
export function showPopup() {
const popupContainer = document.createElement("div");
new Vue({
el: popupContainer,
store,
render: (h) => h(App),
created() {
console.log("Calling store dispatch from popup");
this.$store.dispatch("loadChromePagesStateBrowser");
},
});
}
Where App.vue is
<template>
<div id="app">
<OtherComponent />
</div>
</template>
<script>
import { mapActions } from "vuex";
import OtherComponent from "./components/ChromePage.vue";
export default {
name: "App",
OtherComponent: {
VueTabsChrome,
},
methods: {
...mapActions(["loadChromePagesState"]),
},
mounted() {
// once fully mounted we load data
// this is important for a watcher in ChromePage component
console.log("App.vue mounted");
// this.loadChromePagesState();
},
};
</script>
Intuitively export default new creates a new instance on every import hence the not being in sync across scripts (since the stores are different objects).
How can the same store be initialized once and used across multiple entry points?
popup.js is opened when the user clicks the extension icon:
(in this case clicks "new tab").
I am new to Typescript with vuex. I simply want to fetch user list from the backend. Put in the store. I declared custom user type
export interface User {
id: number;
firstName: string;
lastName: string;
email: string;
}
in my vuex.d.ts file, I declare store module like:
import { Store } from "vuex";
import { User } from "./customTypes/user";
declare module "#vue/runtime-core" {
interface State {
loading: boolean;
users: Array<User>;
}
interface ComponentCustomProperties {
$store: Store<State>;
}
}
in my store I fetch the users successfully and commit the state:
import { createStore } from "vuex";
import axios from "axios";
import { User, Response } from "./customTypes/user";
export default createStore({
state: {
users: [] as User[], // Type Assertion
loading: false,
},
mutations: {
SET_LOADING(state, status) {
state.loading = status;
},
SET_USERS(state, users) {
state.users = users;
},
},
actions: {
async fetchUsers({ commit }) {
commit("SET_LOADING", true);
const users: Response = await axios.get(
"http://localhost:8000/api/get-friends"
);
commit("SET_LOADING", false);
commit("SET_USERS", users.data);
},
},
getters: {
userList: (state) => {
return state.users;
},
loadingStatus: (state) => {
return state.loading;
},
},
});
I set the getters, I sense that I don't need to set getter for just returning state however this is the only way I could reach the data in my component. Please advise if there is a better way to do it. In my component I accessed the data like:
<div class="friends">
<h1 class="header">Friends</h1>
<loading v-if="loadingStatus" />
<div v-else>
<user-card v-for="user in userList" :user="user" :key="user.id" />
<pagination />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { mapGetters } from "vuex";
import { User } from "../store/customTypes/user";
=import UserCard from "../components/UserCard.vue";
import Loading from "../components/Loading.vue";
import Pagination from "../components/Pagination.vue";
export default defineComponent({
name: "Friends",
components: {
UserCard,
Loading,
Pagination,
},
static: {
visibleUsersPerPageCount: 10,
},
data() {
return {
users: [] as User[],
currentPage: 1,
pageCount: 0,
};
},
computed: {
...mapGetters(["loadingStatus", "userList"]),
},
mounted() {
this.$store.dispatch("fetchUsers");
this.paginate()
},
methods: {
paginate () {
// this.users = this.$store.state.users
console.log(this.$store.state.users)
console.log(this.userList)
}
}
});
</script>
Now when I get userList with getters, I successfully get the data and display in the template. However When I want to use it in the method, I can't access it when component is mounted. I need to paginate it in the methods. So I guess I need to wait until promise is resolved however I couldn't figure out how. I tried
this.$store.dispatch("fetchUsers").then((res) => console.log(res)) didn't work.
What I am doing wrong here?
An action is supposed to return a promise of undefined, it's incorrectly to use it like this.$store.dispatch("fetchUsers").then(res => ...).
The store needs to be accessed after dispatching an action:
this.$store.dispatch("fetchUsers").then(() => {
this.paginate();
});
It seems that Vue Meta has been upgraded to handle Vue.js 3 with a new npm package called vue-3-meta
Before Vue.js 3, it was easy to use vue-meta by adding it to the Vue instance:
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
// optional pluginOptions
refreshOnceOnNavigation: true
})
However in Vue.js 3, there is no Vue instance; and instead you create the app by running createApp like such:
const app = createApp(App);
const router = createVueRouter();
app.use(router);
// need to make app use Vue-Meta here
I cannot find any documentation for vue-3-meta. import VueMeta from 'vue-meta' no longer works.
How do I import the vue-3-meta plugin properly and use it with app like in prior versions?
Disclaimer: vue-meta v3 is still in alpha!
This was the minimal implementation I needed to get started:
Update vue-meta to v3 (in package.json)
- "vue-meta": "^2.4.0",
+ "vue-meta": "^3.0.0-alpha.7",
...or with yarn:
yarn add vue-meta#alpha
Add metaManager to Vue app
import { createMetaManager } from 'vue-meta'
const app = createApp(App)
.use(router)
.use(store)
.use(createMetaManager()) // add this line
await router.isReady()
app.mount('#app')
Add <metainfo> to App.vue <template> (this is also where I set a "title template")
<template>
<metainfo>
<template v-slot:title="{ content }">{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}</template>
</metainfo>
<header />
<router-view />
<footer />
</template>
Set default meta in App.vue <script>
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
}
or with vue-class-component:
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class App extends Vue {
meta = setup(() => useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
Override meta in each component
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({ title: 'Some Page' })
}
}
or with vue-class-component:
import { computed } from '#vue/runtime-core'
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class SomePage extends Vue {
meta = setup(() => useMeta(
computed(() => ({ title: this.something?.field ?? 'Default' })))
)
}
See also:
"Quick Usage" (vue-meta next branch)
Vue Router Example (vue-meta next branch)
In addition to the previous answers, I also needed to add a transpileDependency in my vue.config.js, as I was using vue-cli:
module.exports = {
transpileDependencies: ['vue-meta']
}
Else, I would get the error:
error in ./node_modules/vue-meta/dist/vue-meta.esm-browser.min.js
Module parse failed: Unexpected token (8:7170)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Thanks to this thread for pointing me to this: https://stackoverflow.com/a/65844988/3433137
metaManager is a MetaManager instance created from createMetaManager() of vue-meta.
Based on the Vue 3 + Vue Router example for vue-meta, here's an example usage:
import { createApp } from 'vue'
import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
const decisionMaker5000000 = resolveOption((prevValue, context) => {
const { uid = 0 } = context.vm || {}
if (!prevValue || prevValue < uid) {
return uid
}
})
const metaManager = createMetaManager({
...defaultConfig,
esi: {
group: true,
namespaced: true,
attributes: ['src', 'test', 'text']
}
}, decisionMaker5000000)
useMeta(
{
og: {
something: 'test'
}
},
metaManager
)
createApp(App).use(metaManager).mount('#app')
I'm using Vuex stores in a "feature-scope structure" for the first time and have been having difficulties tracing why I am getting a [vuex] unknown getter: $_kp/kp - (Vue/Vuex isn't throwing much of a bone with this other than just the quoted error).
UPDATE: I turned on store.subscribeAction() to see if that give up any more info. Here is the printed log (I'm not seeing any this useful but hopefully it helps you).
Action Type: $_kp/getKpIndex
Action Payload: undefined
Current State: {ob: Observer} $_kp: Object kp: "2" //<- That is what I'm trying to get - "2"!
UPDATE-2: I'm using Vues Inspector now as well and it shows the following:
| State
| - $_kp: object
| - kp: "3"
| Mutation
| - payload: "3"
| - type: "$_kp/KP_DATA_UPDATED"
Any help with this is greatly appreciated and I hope this can be useful for who sets their stores in this manner.
SomeElement.vue:
<script>
import {mapGetters} from 'vuex';
import store from '../_store';
export default {
name : 'KpIndexElement',
parent: 'AVWX',
computed: {
...mapGetters({
kp: '$_kp/kp', //<-- HERE?
}),
},
created() {
const STORE_KEY = '$_kp';
if (!(STORE_KEY in this.$store._modules.root._children)) {//<= I think there is an issue with this too
this.$store.registerModule(STORE_KEY, store);
}
},
mounted() {
this.$store.dispatch('$_kp/getKpIndex');
},
}
</script>
<template>
<p><strong>Kp: </strong>{{ kp }}</p>
</template>
The Store index.js
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
var state = {
kp: '',
};
export default {
namespaced: true,
state,
actions,
getters,
mutations,
};
actions.js:
import api from '../_api/server';
const getKpIndex = (context) => {
api.fetchKpData
.then((response) => {
console.log('fetch response: ' + response)
context.commit('KP_DATA_UPDATED', response);
})
.catch((error) => {
console.error(error);
})
}
export default {
getKpIndex,
}
mutations.js
const KP_DATA_UPDATED = (state, kp) => {
state.kp = kp;
}
export default {
KP_DATA_UPDATED,
}
...and finally the getters.js
const kp = state => state.kp;
export {
kp,
};
Syntax for mapGetters when using namespaces is as follows :
...mapGetters('namespace', [
'getter1',
'getter2',
... // Other getters
])
In your case :
...mapGetters('$_kp', [
'kp'
])
The first argument is the namespace, the second the payload containing the getters you want to use.
Also, as noted in the comments by #Ijubadr, I'm not sure mapGetters is evaluated after you registered your store module. To work around that, you might have to drop the use of mapGetters and declare your STORE_KEY as a data, then define a computed getter using STORE_KEY in its definition (I renamed it storeKey in the example below since this is no longer a constant):
computed: mapState('$_kp',{
kpIndex: 'kp'
}),
created() {
this.storeKey = '$_kp';
if (!(this.storeKey in this.$store._modules.root._children)) {
this.$store.registerModule(this.storeKey, store);
}
},
mounted() {
this.$store.dispatch('$_kp/getKpIndex');
}
I'm learning Vue and I noticed that I have the following syntax more or less everywhere.
export default {
components: { Navigation, View1 },
computed: {
classObject: function() {
return {
alert: this.$store.state.environment !== "dev",
info: this.$store.state.environment === "dev"
};
}
}
}
It's a pain to write out this.$store.state.donkey all the time and it lowers the readability too. I'm sensing that I'm doing it in a less than optimal way. How should I refer to the state of the store?
you can set computed properties for both states & getters i.e.
computed: {
donkey () {
this.$store.state.donkey
},
ass () {
this.$store.getters.ass
},
...
Whilst you still need to call the $state.store once you can then reference a donkey or an ass on your vm...
To make things even easier you can pull in the vuex map helpers and use them to find your ass ... or donkey:
import { mapState, mapGetters } from 'vuex'
default export {
computed: {
...mapState([
'donkey',
]),
...mapGetters([
'ass',
]),
...mapGetters({
isMyAss: 'ass', // you can also rename your states / getters for this component
}),
now if you look at this.isMyAss you'll find it ... your ass
worth noting here that getters, mutations & actions are global - therefore they are referenced directly on your store, i.e. store.getters, store.commit & store.dispatch respectively. This applies whether they are in a module or in the root of your store. If they are in a module check out namespacing to prevent overwriting previously used names: vuex docs namespacing. However if you are referencing a modules state, you must prepend the name of the module, i.e. store.state.user.firstName in this example user is a module.
Edit 23/05/17
Since the time of writing Vuex has been updated and its namespacing feature is now a go to when you work with modules. Simply add namespace: true to your modules export, i.e.
# vuex/modules/foo.js
export default {
namespace: true,
state: {
some: 'thing',
...
add the foo module to your vuex store:
# vuex/store.js
import foo from './modules/foo'
export default new Vuex.Store({
modules: {
foo,
...
then when you are pulling this module into your components you can:
export default {
computed: {
...mapState('foo', [
'some',
]),
...mapState('foo', {
another: 'some',
}),
...
this makes modules very simple and clean to use, and is a real saviour if you are nesting them multiple levels deep: namespacing vuex docs
I have put together an example fiddle to showcase the various ways you can reference and work with your vuex store:
JSFiddle Vuex Example
Or check out the below:
const userModule = {
state: {
firstName: '',
surname: '',
loggedIn: false,
},
// #params state, getters, rootstate
getters: {
fullName: (state, getters, rootState) => {
return `${state.firstName} ${state.surname}`
},
userGreeting: (state, getters, rootState) => {
return state.loggedIn ? `${rootState.greeting} ${getters.fullName}` : 'Anonymous'
},
},
// #params state
mutations: {
logIn: state => {
state.loggedIn = true
},
setName: (state, payload) => {
state.firstName = payload.firstName
state.surname = payload.surname
},
},
// #params context
// context.state, context.getters, context.commit (mutations), context.dispatch (actions)
actions: {
authenticateUser: (context, payload) => {
if (!context.state.loggedIn) {
window.setTimeout(() => {
context.commit('logIn')
context.commit('setName', payload)
}, 500)
}
},
},
}
const store = new Vuex.Store({
state: {
greeting: 'Welcome ...',
},
mutations: {
updateGreeting: (state, payload) => {
state.greeting = payload.message
},
},
modules: {
user: userModule,
},
})
Vue.component('vuex-demo', {
data () {
return {
userFirstName: '',
userSurname: '',
}
},
computed: {
loggedInState () {
// access a modules state
return this.$store.state.user.loggedIn
},
...Vuex.mapState([
'greeting',
]),
// access modules state (not global so prepend the module name)
...Vuex.mapState({
firstName: state => state.user.firstName,
surname: state => state.user.surname,
}),
...Vuex.mapGetters([
'fullName',
]),
...Vuex.mapGetters({
welcomeMessage: 'userGreeting',
}),
},
methods: {
logInUser () {
this.authenticateUser({
firstName: this.userFirstName,
surname: this.userSurname,
})
},
// pass an array to reference the vuex store methods
...Vuex.mapMutations([
'updateGreeting',
]),
// pass an object to rename
...Vuex.mapActions([
'authenticateUser',
]),
}
})
const app = new Vue({
el: '#app',
store,
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<!-- inlining the template to make things easier to read - all of below is still held on the component not the root -->
<vuex-demo inline-template>
<div>
<div v-if="loggedInState === false">
<h1>{{ greeting }}</h1>
<div>
<p><label>first name: </label><input type="text" v-model="userFirstName"></p>
<p><label>surname: </label><input type="text" v-model="userSurname"></p>
<button :disabled="!userFirstName || !userSurname" #click="logInUser">sign in</button>
</div>
</div>
<div v-else>
<h1>{{ welcomeMessage }}</h1>
<p>your name is: {{ fullName }}</p>
<p>your firstName is: {{ firstName }}</p>
<p>your surname is: {{ surname }}</p>
<div>
<label>Update your greeting:</label>
<input type="text" #input="updateGreeting({ message: $event.target.value })">
</div>
</div>
</div>
</vuex-demo>
</div>
As you can see if you wanted to pull in mutations or actions this would be done in a similar way but in your methods using mapMutations or mapActions
Adding Mixins
To extend the above behaviour you could couple this with mixins and then you'd only have to set up the above computed properties once and pull in the mixin on the component that needs them:
animals.js (mixin file)
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState([
'donkey',
...
your component
import animalsMixin from './mixins/animals.js'
export default {
mixins: [
animalsMixin,
],
created () {
this.isDonkeyAnAss = this.donkey === this.ass
...