Update : Mutation recieve the store, not the state - javascript

This post has an update, see the first answer
So, first of all, I did search for similar problems (and found a few threads) but nothing solved my problem. I'm trying to use quasar framework for the first time and maybe I got lost somewhere in the namespaces or something.
So, first, some info :
+I don't have any error when compiling with ESLint
+I don't have any error in my javascript console at runtime
My problem is :
+My actions and mutation do save something in the store, but not where it should (see screenshot at end of post)
+My getter does not seems to work, and is displayed as "undefined" in the vue dev tool
My store is organized like that :
+store [folder]
+ index.js
+ app-utils [folder]
--+ index.js
--+ getters.js
--+ actions.js
--+ mutations.js
--+ state.js
Code of the root index.js :
import Vue from 'vue'
import Vuex from 'vuex'
import appUtils from './app-utils'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
appUtils
}
})
export default store
Then, in the folder 'app-utils' :
Code for index.js :
import state from './state'
import * as getters from './getters'
import * as mutations from './mutations'
import * as actions from './actions'
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
Code for state.js :
export default {
state: {
currentPageTitle: 'Hello'
}
}
Code for getters.js :
export const getPageTitle = (state) => {
console.log('GET TITLE: ' + state.currentPageTitle)
return state.currentPageTitle
}
Code for mutations.js :
export const setPageTitle = (state, newPageTitle) => {
console.log('MUTATION SET TITLE: ' + newPageTitle)
state.currentPageTitle = newPageTitle
}
export const deletePageTitle = (state) => {
console.log('MUTATION DELETE TITLE')
state.currentPageTitle = ''
}
Code for actions.js :
export const setPageTitle = (context, newPageTitle) => {
console.log('ACTION SET TITLE: ' + newPageTitle)
context.commit('setPageTitle', newPageTitle)
}
export const deletePageTitle = (context) => {
console.log('ACTION DELETE TITLE')
context.commit('deletePageTitle')
}
The code from where i am trying to access it (int the getPageTitle computed field):
<template>
<q-page>
<q-resize-observable #resize="onResize" /> TITLE : {{getPageTitle}}
<div class="row">
</div>
</q-page>
</template>
import { mapGetters, mapActions } from 'vuex'
export default {
data: () => ({
pageSize: {
height: 0,
width: 0
}
}),
mounted () {
this.setPageTitle('Template manager')
},
destroyed () {
this.deletePageTitle()
},
computed: {
...mapGetters('appUtils', [
'getPageTitle'
])
},
methods: {
...mapActions('appUtils', [
'setPageTitle',
'deletePageTitle'
]),
onResize (size) {
this.pageSize = size
}
}
}
</script>
<style>
</style>
Finaly, screenshot from the vue plugin , you can see the value has been set upon triggering the mounted() hook, but not in the 'state', and the getter is undefined.
Screenshot from the vue dev plugin

Your state object looks like this:
export default {
state: {
currentPageTitle: 'Hello'
}
}
And that whole thing is being passed to your getter state parameter. That entire exported object is your state, not the "state" property within it. So you have two options:
Option one: Update your getter to access the nested "state" property within your state:
export const getPageTitle = (state) => {
console.log('GET TITLE: ' + state.state.currentPageTitle)
return state.state.currentPageTitle
}
Option two (Probably what you really want to do): change your state object to not have the "state" property.
export default {
currentPageTitle: 'Hello'
}

Update: solved, see selected answer
So I solved my problem. It would appear thatthe first argument sent to my mutation is not the state, but the store itself. So :
This does not work
export const setPageTitle = (state, newPageTitle) => {
console.log('MUTATION SET TITLE: ' + newPageTitle)
state.currentPageTitle = newPageTitle
}
This works
export const setPageTitle = (store, newPageTitle) => {
console.log('MUTATION SET TITLE: ' + store.newPageTitle)
store.state.currentPageTitle = newPageTitle
}
Is this normal behaviour ? The doc seems to say the first argument is supposed to be the state itself.

Related

Make shared property reactive in Vue Composition API composable by declaring variable outside of exported function

I am using the composition api plugin for vue2 (https://github.com/vuejs/composition-api) to reuse composables in my app.
I have two components that reuse my modalTrigger.js composable, where I'd like to declare some sort of shared state (instead of using a bloated vuex state management).
So in my components I do something like:
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'SearchButton',
setup(props, context) {
const { getModalOpenState, setModalOpenState } = modalTrigger();
return {
getModalOpenState,
setModalOpenState,
};
},
};
And in my modalTrigger I have code like:
import { computed, ref, onMounted } from '#vue/composition-api';
let modalOpen = false; // needs to be outside to be accessed from multiple components
export default function () {
modalOpen = ref(false);
const getModalOpenState = computed(() => modalOpen.value);
const setModalOpenState = (state) => {
console.log('changing state from: ', modalOpen.value, ' to: ', state);
modalOpen.value = state;
};
onMounted(() => {
console.log('init trigger');
});
return {
getModalOpenState,
setModalOpenState,
};
}
This works, but only because I declare the modalOpen variable outside of the function.
If I use this:
export default function () {
const modalOpen = ref(false); // <------
const getModalOpenState = computed(() => modalOpen.value);
...
It is not reactive because the modalTrigger is instantiated twice, both with it's own reactive property.
I don't know if that is really the way to go, it seems, that I am doing something wrong.
I also tried declaring the ref outside:
const modalOpen = ref(false);
export default function () {
const getModalOpenState = computed(() => modalOpen.value);
But this would throw an error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
So what would be the correct way to achieve this?
I somehow expected Vue to be aware of the existing modalTrigger instance and handling duplicate variable creation itself...
Well, anyway, thanks a lot in advance for any hints and tipps.
Cheers
Edit:
The complete header.vue file:
<template>
<header ref="rootElement" :class="rootClasses">
<button #click="setModalOpenState(true)">SET TRUE</button>
<slot />
</header>
</template>
<script>
import { onMounted, computed } from '#vue/composition-api';
import subNavigation from '../../../../composables/subNavigation';
import mobileNavigation from '../../../../composables/mobileNavigation';
import search from '../../../../composables/searchButton';
import { stickyNavigation } from '../../../../composables/stickyNav';
import metaNavigation from '../../../../composables/metaNavigation';
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'Header',
setup(props, context) {
const { rootElement, rootClasses } = stickyNavigation(props, context);
mobileNavigation();
subNavigation();
search();
metaNavigation();
const { getModalOpenState, setModalOpenState } = modalTrigger();
onMounted(() => {
console.log('Header: getModalOpenState: ', getModalOpenState.value);
setModalOpenState(true);
console.log('Header: getModalOpenStat: ', getModalOpenState.value);
});
return {
rootClasses,
rootElement,
getModalOpenState,
setModalOpenState,
};
},
};
</script>
The composition API is setup somewhere else where there are Vue components mounted a bit differently than you normally would.
So I can't really share the whole code,but it has this inside:
import Vue from 'vue';
import CompositionApi from '#vue/composition-api';
Vue.use(CompositionApi)
The composition API and every other composable works just fine...

Using Vuex with Nuxt.js - The right way

I try to add Vuex to my Nuxt.js project. I made a custom ligthbox and to share through all pages I write the code into the default layout page.
From a page, to hide or show the ligthbox I need to change the state value. This is all the modules into the "store" folder:
// state.js
export const state = () => ({
lightbox: false,
});
// getters.js
export const getters = {
getLightbox(state)
{
return state.lightbox
},
}
// actions.js
export const actions = {
showLightbox({commit})
{
commit('UPDATE_LIGHTBOX', true)
},
hideLightbox({commit})
{
commit('UPDATE_LIGHTBOX', false)
},
}
// mutations.js
export const mutations = {
UPDATE_LIGHTBOX(state,value)
{
state.lightbox = value
},
}
Then when I try to call "this.$store.state.lightbox" from a method I get an 500 error:
TypeError: Cannot read property 'getters' of undefined
Even I try to use this way and I get the same error:
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getLightbox']),
lightboxState()
{
return this.getLightbox
},
}
mounted()
{
console.log( this.lightboxState )
}
}
Later I try with asyncData but the console print an "undefined" value:
export default {
asyncData({ store })
{
console.log('Store =',store);
}
}
So, what is the right way to using Vuex with Nuxt.js? Maybe I need to add something else to "nuxt.config.js"?
If you're using separate files for state.js, getters.js, mutations.js and actions.js, they should only have a single, default export.
So instead of
// getters.js
export const getters = ...
you should use
// getters.js
export default {
getLightbox: state => state.lightbox
}
This applies to all the files in your store directory.
See https://nuxtjs.org/guide/vuex-store/#modules-mode

Vue/Nuxt/Vuex - [NUXT:SSR] [ERROR] [vuex] unknown getter

The error appears when I use a v-for loop to go through the 'allPosts' data on my div.
The Nuxt documentation says 'Modules: every .js file inside the store directory is transformed as a namespaced module'. Maybe I'm missing something in this regard?
pages/index.vue
<template>
<section id="postgrid">
<div v-for="post in allPosts" :key="post.id"></div>
</section>
</template>
<script>
import {mapGetters} from 'vuex'
import PostTile from '#/components/Blog/PostTile'
export default {
components: {
PostTile
},
computed: mapGetters(['allPosts'])
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import Posts from './posts'
const store = new Vuex.Store({
modules: {
Posts
}
})
store/posts.js
const state = () => ({
posts: [
{
id: 0,
title: 'A new beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Food',
featured_image: 'http://getwallpapers.com/wallpaper/full/6/9/8/668959.jpg',
slug: 'a-new-beginning',
post_body: '<p>Post body here</p>',
next_post_slug: 'a-second-beginning'
},
{
id: 1,
title: 'A second beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Venues',
featured_image: 'https://images.wallpaperscraft.com/image/beautiful_scenery_mountains_lake_nature_93318_1920x1080.jpg',
slug: 'a-second-beginning',
post_body: '<p>Post body here</p>',
prev_post_slug: 'a-new-beginning',
next_post_slug: 'a-third-beginning'
},
{
id: 2,
title: 'A third beginning',
previewText: 'This will be awesome don\'t miss it',
category: 'Experiences',
featured_image: 'http://eskipaper.com/images/beautiful-reflective-wallpaper-1.jpg',
slug: 'a-third-beginning',
post_body: '<p>Post body here</p>',
prev_post_slug: 'a-second-beginning',
next_post_slug: 'a-forth-beginning'
}
]
})
const getters = {
allPosts: (state) => state.posts
}
export default {
state,
getters
}
You have a number of issues in how you are setting up and accessing your store. Firstly you are creating your store using the "classic mode" which the docs tell us:
This feature is deprecated and will be removed in Nuxt 3.
So in order to be using the latest methods your store/index.js should look like this:
//store/index.js
//end
This is not a mistake, you don't actually need anything in it, just have it exist. There is no need to import vue or vuex or any modules.
Your store/posts.js can largely stay as it is, just change your state, mutations, getters, and actions to be exported constants and delete the bottom export:
//store/posts.js
export const state = () => ({
posts: [
...
]
})
export const mutations = {
}
export const actions = {
}
export const getters = {
allPosts: state => state.posts
}
//delete the following
export default {
state,
getters
}
Secondly you seem to be using mapGetters incorrectly. If you set up your store like I have above, you can use it in pages/index.vue like so:
//pages.index.vue
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters ({
allposts: 'posts/allPosts'
})
}
}
</script>
Then you can access "allPosts" in your template as you would any computed property or access it with "this.allPosts" in your script.

How do I export a string to another Vue file?

I have a masterData.js file that is a store for my master data, in short the file reads my mongo db data & sends it to other project components. I created a function to export the string in the masterData.js file as:
/ ***************************** MUTATIONS
const mutations = {
exportColumns (payload) {
Object.keys(payload[0]).map(x => { return x; });
}
}
Where payload will store all the rows and payload[0] holds the value of column header names. The output of this chunk of code is like this:
["_id","businessAreaName","businessAreaDisplay","councilDisplay","councilID"]
I want to transfer the values to masterData.vue file. My code on masterData.Vue is:
importColumns ()
{
let payload = {
vm: this,
mutation: 'masterData/exportColumns'
};
}
What else should I add to to check whether the column names are received or not?
If you're trying to access the data in your store from within a component, then you'll want to either just map the state to the component or map a getter to the component. Mutations are used by components (or by actions) to modify the state of the store. So instead you would do something like...
//masterData.js
//assuming this gets rolled up as a module called masterdata to the primary store
//store for payload state
const state = {
payload: null,
}
//allows payload to be set -- not sure how you are retrieving the payload but you can use this to store it however you get it
const mutations = {
setPayload (state, payload) {
state.payload = payload
}
}
//get just the columns
const getters = {
getColumns (state) {
Object.keys(state.payload[0]).map(x => { return x; })
}
}
export default {
state,
mutations,
getters,
}
Then
//masterData.vue
<template>
//...
</template>
<script>
import { mapGetters, mapState } from 'vuex'
export default {
computed: {
//I believe getting state from a store module requires a function like this
...mapState({
payload: function(state) {
return state.masterdata.payload
},
}),
//I think for getters you can just reference the method and it will find it
...mapGetters([
'getColumns',
])
},
}
</script>
This is how you import stuff in a single file component.
<template>
<!-- html stuff -->
</template>
<script>
import Mutations from 'yourModule.js'
export default {
name: 'YourComponent',
props: {},
data(){
return {
foo: 'foo'
}
},
methods{
mymethod() {
Mutations.exportColumn(this.foo);
},
}
}
</script>

How to get vuex state from a javascript file (instead of a vue component)

I am working with vuex (2.1.1) and get things working within vue single file components. However to avoid too much cruft in my vue single file component I moved some functions to a utils.js module which I import into the vue-file. In this utils.js I would like to read the vuex state. How can I do that? As it seems approaching the state with getters etc is presuming you are working from within a vue component, or not?
I tried to import state from '../store/modules/myvuexmodule' and then refer to state.mystateproperty but it always gives 'undefined', whereas in the vue-devtools I can see the state property does have proper values.
My estimate at this point is that this is simply not 'the way to go' as the state.property value within the js file will not be reactive and thus will not update or something, but maybe someone can confirm/ prove me wrong.
It is possible to access the store as an object in an external js file, I have also added a test to demonstrate the changes in the state.
here is the external js file:
import { store } from '../store/store'
export function getAuth () {
return store.state.authorization.AUTH_STATE
}
The state module:
import * as NameSpace from '../NameSpace'
/*
Import everything in NameSpace.js as an object.
call that object NameSpace.
NameSpace exports const strings.
*/
import { ParseService } from '../../Services/parse'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
}
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
ParseService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
export default {
state,
getters,
mutations,
actions
}
The store:
import Vue from 'vue'
import Vuex from 'vuex'
import authorization from './modules/authorization'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
authorization
}
})
So far all I have done is create a js file which exports a function returning the AUTH_STATE property of authorization state variable.
A component for testing:
<template lang="html">
<label class="login-label" for="username">Username
<input class="login-input-field" type="text" name="username" v-model="username">
</label>
<label class="login-label" for="password" style="margin-top">Password
<input class="login-input-field" type="password" name="username" v-model="password">
</label>
<button class="login-submit-btn primary-green-bg" type="button" #click="login(username, password)">Login</button>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import * as NameSpace from '../../store/NameSpace'
import { getAuth } from '../../Services/test'
export default {
data () {
return {
username: '',
password: ''
}
},
computed: {
...mapGetters({
authStateObject: NameSpace.AUTH_GETTER
}),
authState () {
return this.authStateObject.auth
},
authError () {
return this.authStateObject.error
}
},
watch: {
authError () {
console.log('watch: ', getAuth()) // ------------------------- [3]
}
},
authState () {
if (this.authState.sessionToken) {
console.log('watch: ', getAuth()) // ------------------------- [2]
}
},
methods: {
...mapActions({
authorize: NameSpace.ASYNC_AUTH_ACTION
}),
login (username, password) {
this.authorize({username, password})
console.log(getAuth()) // ---------------------------[1]
}
}
}
</script>
On the button click default state is logged on to the console. The action in my case results in an api call, resulting a state change if the username - password combination had a record.
A success case results in showing the console in authState watch, the imported function can print the changes made to the state.
Likewise, on a fail case, the watch on authError will show the changes made to the state
For anyone wondering how to access a mutation from a javascript file, you can do the following:
import store from './store'
store.commit('mutation_name', mutation_argument);
Or for actions,
store.dispatch('action_name', action_argument)
import store from './store'
and than
store.commit('mutation_name', mutation_argument)
if you use js file
You can also access actions like:
import store from './store'
store.dispatch('action_name', action_argument)

Categories

Resources