I'm trying Quasar for the first time and trying to use the Vuex with modules but I can't access the $store property nor with ...mapState. I get the following error 'Cannot read property 'logbook' of undefined' even though I can see that the promise logbook exists on Vue Devtools. Print from Devtools
Here is my store\index.js
import Vue from 'vue';
import Vuex from 'vuex';
import logbook from './logbook';
Vue.use(Vuex);
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
logbook,
},
strict: process.env.DEV,
});
return Store;
}
Here is the component
<template>
<div>
<div>
<h3>RFID</h3>
<q-btn #click="logData"
label="Save"
class="q-mt-md"
color="teal"
></q-btn>
<q-table
title="Logbook"
:data="data"
:columns="columns"
row-key="uid"
/>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
export default {
name: 'RFID',
mounted() {
this.getLogbookData();
},
methods: {
...mapActions('logbook', ['getLogbookData']),
...mapGetters('logbook', ['loadedLogbook']),
...mapState('logbook', ['logbookData']),
logData: () => {
console.log(this.loadedLogbook);
},
},
data() {
return {
};
},
};
</script>
<style scoped>
</style>
Here is the state.js
export default {
logbookData: [],
};
Error that I get on the console
Update: Solved the problem by refactoring the way I declared the function. I changed from:
logData: () => { console.log(this.loadedLogbook); }
to
logData () { console.log(this.loadedLogbook); }
Check the .quasar/app.js file. Is there a line similar to import createStore from 'app/src/store/index', and the store is later exported with the app in that same file?
I think you confused all the mapx functions.
...mapState and ...mapGetters provide computed properties and should be handled like this
export default {
name: 'RFID',
data() {
return {
};
},
mounted() {
this.getLogbookData();
},
computed: {
...mapGetters('logbook', ['loadedLogbook']),
...mapState('logbook', ['logbookData']),
}
methods: {
...mapActions('logbook', ['getLogbookData']),
logData: () => {
console.log(this.loadedLogbook);
},
}
};
Related
The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>
I know this seems like a question that would be easy to find, but my code worked some time ago. I am using a Vuex binding to check if my sidebar should be visible or not, so stateSidebar should be set within my entire project.
default.vue
<template>
<div>
<TopNav />
<SidebarAuth v-if="stateSidebar" />
<Nuxt />
</div>
</template>
<script>
import TopNav from './partials/TopNav';
import SidebarAuth from './partials/SidebarAuth';
export default {
components: {
TopNav,
SidebarAuth
},
methods: {
setStateSidebar(event, state) {
this.$store.dispatch('sidebar/setState', state)
}
}
}
</script>
store/sidebar.js
export const state = () => ({
stateSidebar: false
});
export const getters = {
stateSidebar(state) {
return state.stateSidebar;
}
};
export const mutations = {
SET_SIDEBAR_STATE(state, stateSidebar) {
state.stateSidebar = stateSidebar;
}
};
export const actions = {
setState({ commit }, stateSidebar) {
commit('SET_SIDEBAR_STATE', stateSidebar);
},
clearState({ commit }) {
commit('SET_SIDEBAR_STATE', false);
}
};
plugins/mixins/sidebar.js
import Vue from 'vue';
import { mapGetters } from 'vuex';
const Sidebar = {
install(Vue, options) {
Vue.mixin({
computed: {
...mapGetters({
stateSidebar: 'sidebar/stateSidebar'
})
}
})
}
}
Vue.use(Sidebar);
nuxt.config.js
plugins: ["./plugins/mixins/validation", "./plugins/axios", "./plugins/mixins/sidebar"],
If you're creating a mixin, it should be in /mixins
So for example /mixins/my-mixin.js.
export default {
// vuex mixin
}
Then import it like this in default.vue
<script>
import myMixin from '~/mixins/my-mixin`
export default {
mixins: [myMixin],
}
This is not what plugins should be used for tho. And IMO, you should definitively make something simpler and shorter here, with less boilerplate and that will not be deprecated in vue3 (mixins).
This is IMO the recommended way of using it
<template>
<div>
<TopNav />
<SidebarAuth v-if="stateSidebar" />
<Nuxt />
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState('sidebar', ['stateSidebar']) // no need to use object syntax nor a getter since you're just fetching the state here
},
}
</script>
No mixin, no plugin entry.
I've mapped chartData to a state property using vuex. What I'd like to do is update the chart when a dataset is updated. I have heard that it can be done with mixins or watchers but I don't know how to implement it. I understand that mixins creates a watcher but I don't know how it is used within vuex.
Chartline.vue:
<script>
import { Line } from 'vue-chartjs'
import { mapState } from 'vuex'
export default {
name: 'ChartLine',
extends: Line,
computed:{
...mapState(['charData','options'])
},
methods:{
regraph: function(){
this.renderChart(this.charData,this.options);
}
},
mounted () {
this.regraph();
},
watch: {
}
}
</script>
Pension.vue:
<template>
<div id='pension' class="navbarPar">
<ChartLine/>
</div>
</template>
<script>
import ChartLine from '../components/ChartLine.vue';
import { mapState } from 'vuex'
//import { Line } from 'vue-chartjs'
export default {
name: 'Pension',
components: {
ChartLine,
},
data(){
return{
form: {
...
},
var:{
...
},
}
},
methods: {
calculate: function(indice){
...
//modify data of mapState
//after here, I want to rerender chart
}
},
computed:{
...mapState(['charData','options']),
},
}
</script>
Using a watcher like this should be enough:
<script>
import { Line } from "vue-chartjs";
import { mapState } from "vuex";
export default {
name: "ChartLine",
extends: Line,
computed: {
...mapState(["chartData", "options"])
},
methods: {
regraph() {
this.renderChart(this.chartData, this.options);
}
},
mounted() {
this.regraph();
},
watch: {
chartData: {
handler: this.regraph,
deep: true
}
}
};
</script>
Also having the explicit vuex state map inside the ChartLine component seems a bit wasteful - passing the vuex data through props would render the component more generic:
<template>
<div id='pension' class="navbarPar">
<ChartLine :options="options" :chart-data="chartData"/>
</div>
</template>
<script>...
Chartline.vue:
<script>
import { Line } from "vue-chartjs";
export default {
name: "ChartLine",
extends: Line,
props: {
options: {
type: Object,
default: () => ({})
},
chartData: {
type: Object /*is it?*/,
default: () => ({})
}
},
methods: {
regraph() {
this.renderChart(this.chartData, this.options);
}
},
mounted() {
this.regraph();
},
watch: {
chartData: {
handler: this.regraph,
deep: true
}
}
};
</script>
If you are using vue-chartjs, the library has its own way to handle reactive data in charts:
// ChartLine.js
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'], // passed from the parent
mounted () {
// this.chartData is created in the mixin (pass it as any prop with :chart-data="data").
this.renderChart(this.chartData, this.options)
}
}
Now the Pension.vue file
// Pension.vue
<template>
<div id='pension' class="navbarPar">
<ChartLine :chart-data="charData" :options="options" />
</div>
</template>
<script>
import ChartLine from '../components/ChartLine';
import { mapState } from 'vuex'
export default {
name: 'Pension',
components: {
ChartLine,
},
data(){
return{
form: {
...
},
var:{
...
},
}
},
methods: {
calculate: function(indice){
...
//modify data of mapState
//after here, I want to rerender chart
}
},
computed:{
...mapState(['charData','options']),
},
}
</script>
You can read more about it here: https://vue-chartjs.org/guide/#updating-charts,
there are some caveats
I'm trying to render a d3 graph using stored data in vuex. but I'm not getting data in renderGraph() function.
how to get data in renderGraph()?
Following is store methods.
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import * as d3 from "d3";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
subscribers: []
},
getters: {
getterSubscribers: state => {
return state.subscribers;
}
},
mutations: {
mutationSubscribersData: (state, payload) => {
state.subscribers = payload;
}
},
actions: {
actionSubscribersData: async ({ commit }) => {
let subsData = await d3.json("./data/subscribers.json"); // some json fetching
commit("mutationSubscribersData", subsData);
}
}
});
Below is parent component
Home.vue
<template>
<div>
<MyGraph /> // child component rendering
</div>
</template>
<script>
import MyGraph from "./MyGraph.vue";
export default {
components: {
MyGraph
},
};
</script>
Below is child component.
MyGraph.vue
<template>
<div>
<svg width="500" height="400" />
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
computed: {
...mapGetters(["getterSubscribers"])
},
methods: {
...mapActions(["actionSubscribersData"]),
renderGraph(data) {
console.log(data); // DATA NOT COMING HERE
// MyGraph TO BE RENDERED WITH THIS DATA
}
},
mounted() {
this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
};
</script>
I have tried mounted, created lifecycle hooks. but did not find data coming.
There is race condition. actionSubscribersData is async and returns a promise. It should be waited for until data becomes available:
async mounted() {
await this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
There must be delay for the actionSubscribersData action to set value to store. Better you make the action async or watch the getter. Watching the getter value can be done as follows
watch:{
getterSubscribers:{ // watch the data to set
deep:true,
handler:function(value){
if(value){ // once the data is set trigger the function
this.renderGraph(value);
}
}
}
}
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>