Using Vuex with Nuxt.js - The right way - javascript

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

Related

Uncaught error when trying to embed a Vega chart in Vue + Vuex project

I am trying to use vega-embed within a Vue.js (together with vuex for state management) project. Basically the backend serves a Vega json object which is picked up by the frontend via HTTP GET request with a click event. However I have to click twice to get the plot displayed and the first click event always triggers an error "Uncaught (in promise) TypeError: Cannot read property '$schema' of null". Can someone help me debug? Very much appreciated. Details shown below:
The vue component file:
<template>
<button #click.native="fetchCars(); displayVegaPlot()">fetch cars</button>
<div id="vega-example"></div>
</template>
<script>
import {default as vegaEmbed} from 'vega-embed'
import {
mapState
} from 'vuex'
export default {
name: 'VegaExample',
props: {
component_msg: String
},
methods: {
fetchCars () {
this.$store.dispatch('fetchCars')
},
displayVegaPlot () {
vegaEmbed('#vega-example', this.vega_cars, {actions: false})
}
},
computed: {
...mapState([
'vega_cars'
])
}
}
</script>
... and the store js file:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
state: {
error: '',
vega_cars: null
},
mutations: {
SET_CARS: (state, cars) => {
state.vega_cars = cars
},
SET_ERROR: (state, error) => {
state.error = error
}
}
actions: {
fetchCars: (context) => {
axios.get(`vega_cars`)
.then(response => context.commit('SET_CARS', response.data))
.catch(error => context.commit('SET_ERROR', error))
}
}
Thanks a lot for the comment from #TommyF, after some doc reading I think I figured out the solution (apparently as a newcomer to web app dev and Vue I didn't know Vue offers a special watch facility). So in the component Vue file, instead of declaring a method displayVegaPlot to call imperatively, a watch can be setup to do the display the Vega plot, as soon as vega_cars changes value:
watch: {
vega_cars: (spec) => {
console.log('$store.state.vega_cars changed value')
if (spec) {
vegaEmbed('#vega-example', spec, {actions: false})
}
}
}
and of course ...mapState(['vega_cars']) needs to be put in computed.

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>

Update : Mutation recieve the store, not the state

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.

Pass prop as module name when mapping to namespaced module

I'm trying to pass the store module namespace via props to a component. When I try and map to getters with the prop, it throws this error,
Uncaught TypeError: Cannot convert undefined or null to object
If I pass the name as a string it works.
This Works
<script>
export default {
props: ['store'],
computed: {
...mapGetters('someString', [
'filters'
])
}
}
</script>
This does not work
this.store is defined
this.store typeof is a String
<script>
export default {
props: ['store'],
computed: {
...mapGetters(this.store, [
'filters'
])
}
}
</script>
I used this style utilising beforeCreate to access the variables you want, I used the props passed into the component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import { createNamespacedHelpers } from "vuex";
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above with no need to drop namespaces everywhere
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
})
}
}
}
Hope you find this useful.
I tackled this problem for hours, too. Then I finally came up with one idea.
Add attachStore function in a child vue component. A function nama is not important. Any name is ok except vue reserved word.
export default {
:
attachStore (namespace) {
Object.assign(this.computed, mapGetters(namespace, ['filters']))
}
}
When this vue component is imported, call attachStore with namespace parameter. Then use it at parent components attributes.
import Child from './path/to/child'
Child.attachStore('someStoresName')
export default {
name: 'parent',
components: { Child }
:
}
The error you're encountering is being thrown during Vue/Vuex's initialization process, this.store cannot be converted because it doesn't exist yet. I haven't had to work with namespacing yet, and this is untested so I don't know if it will work, but you may be able to solve this problem by having an intermediary like this:
<script>
export default {
props: ['store'],
data {
namespace: (this.store !== undefined) ? this.store : 'null',
},
computed: {
...mapGetters(this.namespace, [
'filters'
])
}
}
</script>
That ternary expression will return a string if this.store is undefined, if it isn't undefined then it will return the value in this.store.
Note that there is also a discussion about this on Vue's Github page here: https://github.com/vuejs/vuex/issues/863
Until Vue formally supports it, I replaced something like
...mapState({
foo: state => state.foo
})
with
foo () {
return this.$store.state[this.namespace + '/foo'] || 0
}
Where namespace is passed to my child component using a prop:
props: {
namespace: { type: String, required: true }
}

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