I am new to Pinia, and am having trouble setting up just a basic store. I have been following Pinia's own documentation, and cannot seem to read any state whatsoever from the vue component I'm mapping to the Pinia store.
In my app I have:
import { createPinia } from 'pinia';
export default function initApp(el) {
let app = createApp(MenuApp);
app.use(router).use(createPinia()).mount(el);
}
I set up a super basic Pinia store, just to get started:
import {defineStore} from 'pinia';
export const useTestPiniaStore = defineStore('testStore', {
state: () => {
return {
name: 'bob'
}
},
})
In my vue component I have:
<template>
<div class="menu-page">
<h1>{{name}}</h1>
</div>
</template>
<script>
import { mapState } from 'pinia';
import useTestPiniaStore from '#store/modules/piniaStore';
export default {
computed: {
...mapState(useTestPiniaStore['name']),
}
}
</script>
Pinia appears in my Vue dev tools, but no stores appear within it, and I get the error
Cannot read properties of undefined (reading 'name')
I don't understand what I am doing wrong here. If anyone can give some pointers that would be so appreciated.
mapState() requires two arguments, but you've passed it only one.
The 1st argument should be useTestPiniaStore, and the 2nd should be an array of state properties to map (or an object). It looks like you're trying to reference name from useTestPiniaStore, which would be undefined.
Your computed prop should look like this:
<script>
import { mapState } from 'pinia'
import { useTestPiniaStore } from '#/store'
export default {
computed: {
...mapState(useTestPiniaStore, ['name']), 👈
},
}
</script>
demo
Related
While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!
While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!
How can I get access to the component props before the component will be rendered?
I would like to load the google maps api with the value of the passed prop.
import * as VueGoogleMaps from 'vue2-google-maps';
import GmapCluster from 'vue2-google-maps/dist/components/cluster';
import Vue from 'vue';
Vue.use(VueGoogleMaps, {
load: {
// Get here the prop apiKey
key: '..........',
libraries: 'places', in
}
});
Vue.component('GmapCluster', GmapCluster);
export default {
name: 'api',
props: ['apiKey'],
methods: {
},
created() {
}
}
Hmn I've never heard about it and the project page is asking for contributers and this lib has more than 100 issues. So my suggestion is change the lib. I've made good experiences with https://github.com/KoRiGaN/Vue2Leaflet
You can do it on beforeMount() lifecycle hook.
beforeMount() {
loadGmapApi({
key: apiKeyProp
});
}
I was using vuetify and wanted to change theme from vuex store using $vuetify instance but i got this error Cannot set property 'theme' of undefined"
here is my code
export default {
getters: {},
mutations: {
toggleDarkTheme(state) {
this.$vuetify.theme.primary = "#424242";
}
}
};
For Vuetify 2.0 you can try following method. (After following Vuetify 2.0 Upgrade guide for themes)
import Vuetify from './plugins/vuetify'
export default {
getters: {},
mutations: {
toggleDarkTheme(state) {
Vuetify.framework.theme.themes.light.primary = "#424242";
}
}
$vuetify is an instance property hence you can access any vue
instance property using
Vue.prototype.$prop
For your case
import Vue from 'vue';
export default {
getters: {},
mutations: {
toggleDarkTheme(state) {
Vue.prototype.$vuetify.theme.primary = "#424242";
}
}
};
This one worked for me
...
toggleDarkTheme(state) {
window.$nuxt.$root.$vuetify.theme.dark = true
}
For Nuxt.js projects with Vuetify set as a buildModule, you can access $vuetify from the $nuxt property in the Vue instance:
import Vue from 'vue';
export actions = {
yourAction() {
Vue.prototype.$nuxt.$vuetify.theme.dark = true;
}
}
I'm working on a vue single page project,and I use an empty Vue instance as a central event bus.But there is some problem when firing a event.
eventbus.js
import vue from 'Vue'
export default new vue({})
a.vue
import bus from '~js/eventBus'
methods: {
go(name) {
bus.$emit('setPartner', name);
this.$router.go(-1);
}
}
b.vue
import bus from '~js/eventBus'
data() {
return {
contract: {
contractSubject: ''
}
}
},
mounted(){
bus.$once('setPartner', data => {
this.contract.contractSubject = data;
});
}
in b.vue file,I can recieve data,but I can't assign the value of data to 'this.contract.contractSubject'
I would comment, but point restrictions. It seems like you're only posting snippets of the code, so it's hard to get a full picture. I assume that you've already set this.contract to be an Object? I don't know what your data function looks like, and an error message would be helpful, but it sounds like you're trying to assign a field on an object that doesn't exist yet.
Edit
Thanks for the information. I'm not sure if your edit to b.vue was a mistake when you copied things over to stackoverflow, but based on the code you've provided, my guess is that you wrote data() in the wrong place. You have it inside mounted(), not as a key value of the component object, and thus this.contract won't access it.
I managed to get it working under the setup below
a.vue
import bus from './bus.js';
export default {
name: 'sitea',
methods: {
go(name) {
bus.$emit('setPartner', name);
}
}
}
b.vue
import bus from './bus.js'
export default {
name: 'siteb',
data() {
return {
contract: {
contractSubject: ''
}
}
},
mounted() {
bus.$once('setPartner', data => {
console.log(data);
this.contract.contractSubject = data;
});
}
}
More information