Use ES6 classes as vuex store state in Nuxt - javascript

When I set ES6 class to state on my vuex store in nuxt I got following warn:
WARN Cannot stringify arbitrary non-POJOs EndPoint
and When I use object as state is works without warning.
So How can I use ES6 classes in my state.
My model:
export default class EndPoint {
constructor(newEndPoints) {
this.login = newEndPoints.login;
this.status = newEndPoints.status;
}
}
and mutate state here:
commit(CoreMutations.SET_ENDPOINTS, new EndPoint(response.data));
Using object:
const EndPoint = {
endPoints(newEndPoints) {
return {
login: newEndPoints.login,
status: newEndPoints.status
};
}
};
and mutate:
commit(CoreMutations.SET_ENDPOINTS, EndPoint.endPoints(response.data));

As discussion in comment add toJSON method in class solve the problem when setting state in client, but when set state in server, states will become undefined.
so adding this code solve the problem:
toJSON() {
return Object.getOwnPropertyNames(this).reduce((a, b) => {
a[b] = this[b];
return a;
}, {});
}

Related

Methods missing from Javascript Class inside Vuex state

I'm using Nuxt framework alongside Vuex to store data in my web site but I'm facing trouble when I want to use a class directly in the state.
With a model cart.js defined like this:
export class Cart {
constructor(ownedID) {
this._created = new Date();
this._lastUpdated = new Date();
this._ownerID = ownedID || 'visitor'
this._items = []
}
getItem (articleNumber) {
console.log(this._items)
}
...
}
And my store's module cart.js
import { Cart } from "~/models/cart";
const state = () => ({
cart: new Cart()
})
const mutations = {
ADD_ITEM(state, newItem) {
console.log(state.cart)
}
}
...
When the ADD_ITEM(state, newItem) mutation is called the getItem(articleNumber) function is missing and thus I receive the TypeError: state.cart.getItem is not a function error.
This is the result of the console.log:
__ob__: Object { value: {…}, dep: {…}, vmCount: 0 }
​
_created:
_item:
_lastUpdated:
_ownerID:
This is a sandbox link of my setup.
Nuxt vuex sandbox error
Does anyone have a clue about my issue.
Thank you.
Vue accepts only plain objects & Observes only native object properties, It ignores the prototype properties. According to the Vue documentation
The object must be plain: native objects such as browser API objects and prototype properties are ignored
In your case, your using a class which creates variables in plain object and methods in prototype(__proto__), That's why state is unable to find getItem method. You need to use plain objects instead of classes
.

Vuex: what is the best way to update multiple state properties in VUEX mutation?

I have a namespaced Vuex mutation and I am trying to update multiple state properties at once. In order to avoid repetition I would like to use the ES6 spread operator. Object.assign works as expected but the spread operator fails to update the VUEX state. Here is the code which illustrates the problem:
const authorizationModule = {
namespaced: true,
state: {
status: '',
jwt: '',
user: {
email: ''
}
},
mutations: {
AUTH_SUCCESS(state, payload) {
const { jwt, user } = payload;
// Too much repitition (needs to be abstracted)
state.jwt = jwt;
state.status = 'status';
state.user = user;
// 1. Object.assign works correctly
// state = Object.assign(state, {
// jwt,
// status: 'success',
// user
// });
// 2. Spread operator does not work
// let newState = {
// ...state,
// jwt,
// status: 'success',
// user
// }
// state = newState
},
}
}
It's interesting because I threw a debugger into the code and inspected it in the browser. It seems like the return value for the spread operator is returning a different object. The Object.assign implementation seems to have getter and setter functions in the object (which is the reason it is working as intended). Here is an image of my console:
You can see that the object in the 2nd example is missing certain Vuex getter and setting methods.
So the question remains: is it possible to use the spread operator in this context to update multiple state properties in Vuex?

Vue/Vuex - Module two depends on module one, and module one gets data from server

Check this out:
import accountModule from '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
export default new Vuex.Store({
modules: {
account: accountModule,
other: otherModule,
}
});
The data initialization in other depends on the account module because the account module has user specific settings. Suppose other.state.list depends on account.state.settings.listOrder. However, I want the data for the account module to come from the server. Which is async. So when other is trying to get set up, it can't just try to reference account.state.settings.listOrder because the response from the server may not have come back yet.
I tried exporting a promise in accountModule that resolves with the module itself. But that approach doesn't seem to work.
import accountModulePromise from '#/store/modules/account/account';
accountModulePromise.then(function (accountMoudle) {
import otherModule from '#/store/modules/other/other';
...
});
This gives me an error saying that import statements need to be top level.
The following doesn't work either:
let accountModule = await import '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
...
It gives me an error saying that await is a reserved word. I'm confused though, because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import says that I should be able to do it.
Your last code block didn't work because of await have to be inside async function.
Remember, the await keyword is only valid inside async functions. If
you use it outside of an async function's body, you will get a
SyntaxError.
From MDN.
You can use Dynamic Module Registration:
accountModulePromise.then(async () => {
let otherModule = await import('#/store/modules/other/other');
store.registerModule('other', otherModule.default);
});
But when you want to get state or dispatch actions you have to check whether module is registered which is pretty bad.
In my opinion it would be better if you redesign your module structure to decoupling each other. Try to move your initialize code to main.js or App.vue then dispatch actions to update module states from that.
Updates
From your last update, Another idea to decoupling your store, I think you should store your list without order and sort it only when you use. You can do this with:
Computed property:
...
computed: {
list () {
let list = this.$store.state.other.list
let order = this.$store.state.account.settings.listOrder
if (!list || !order) return []
return someSort(list, order)
}
},
beforeCreate () {
this.$store.dispatch('other/fetchList')
this.$store.dispatch('account/fetchListOrder')
}
...
Or Vuex getters:
...
getters: {
list: (state) => (order) => {
return someSort(state.list, order)
}
}
...
...
computed: {
list () {
let order = this.$store.state.account.settings.listOrder
return this.$store.getters['others/list'](order)
}
}
...
Okay, so you have two modules. One with state that is fetched from the server, the other with state that is dependent on the first, correct?
I would suggest the following approach:
Set up your modules with empty 'state' to begin with. Then create an action within accountModule to set up the state from the server. Use a getter on other to order the list. Finally, dispatch your action upon app creation.
const account = {
namespaced: true,
state: {
listOrder: ''
},
mutations: {
setListOrder (state, newListOrder) {
state.listOrder = newListOrder
}
},
actions: {
async fetchServerState (ctx) {
let result = await fetch("/path/to/server")
ctx.commit('setListOrder', result.listOrder)
// or whatever your response is, this is an example
}
}
}
const other = {
namespaced: true,
state: {
unorderedList: []
},
getters: {
list (state, getters, rootState) {
return someSort(state.unorderedList, rootState.account.listOrder);
}
}
}
within App.vue (or wherever)
created () {
this.$store.dispatch('account/fetchServerState')
}

Vuex: Call getters from action

Is there a way for a dispatch/action to call a getter inside of it?
mutations: {
setData(state, data) {
state.data = data;
}
}
actions: {
sendDataToServer({ commit }, payload) {
// call getter (data) and assign to variable
// do async functions from the data returned
}
},
getters: {
getAppData: state => () => {
return state.data;
}
}
So what's the best practice here? Using the mutation to change the state and then get the state and pass it to action which will then execute the async function or do I need to restructure my implementation?
call mutation -> get the data via getter -> call action
OR
do it all on the action (do mutation on the action and do the action/async method without the need of the getter)?
In addition to commit, actions has default injected parameters which are dispatch, getters and rootGetters. So you can simply write;
sendDataToServer({ commit, getters }, payload) to access getters.
You have access to getters inside an action:
getters: {
getUser(state){
return state.user
}
}
actions : {
myAction({ getters }){
let user = getters.getUser
}
}
In the action, you see the first parameter has {commit} in it. Similarly, you can pass {commit, state}. This way, you can directly access the state.data.
I think in your example, you would want to do the action because you can call the mutation from inside action itself using commit('setData').
The first parameter is there for you to use state and mutation as you prefer. Personally, I have only worked on projects where you do the action first and do mutation to store it in the app. For example, if I want to store a car info in the server somewhere, first I would do the action (and save it to remote db). Once I confirm that it saved in db, I would locally mutate in the store. This totally depends on case by case basis. But good thing is that you can mutate from inside the action
Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call context.commit to commit a mutation, or access the state and getters via context.state and context.getters
actions: {
sendDataToServer(context, payload) {
// context object contains state, commit, getters
context.getters.getAppData
}
},
Refer docs: https://vuex.vuejs.org/guide/actions.html#dispatching-actions
If you are using nuxt and isolated files in vuex, like this =
store -
|
|-- index.js
|
|-- store.js
|
|-- product.js
// store.js
export const getters = {
getNameStore: state => state.getNameStore ? state.getNameStore : null
};
I want the getNameStore of the store.js into product.js
// product.js
export const actions = {
setResultSearch({ commit, dispatch }, text) {
console.log(
'getNameStore',
this.getters["store/getNameStore"]
);
};
this.getters["store/getNameStore"]

Vuejs - Vuex behavior when committing a property

I'm noticing a weird behavior in Vuex when I try to mutate a property in the state obj.
Example:
Mutation: {
authUser: (state, payload) => {
state.email = payload.email
state.password = payload.password
...someOtherProps
}
actions: {
commit ('authUser', {
email: 'user#gmail.com'
})
}
What I noticed is that when I commit only one property(in this case "email"), the value of all other properties of the authUser will be undefined and only email value will be available.
Is that the way Vuex behaves in this case? If yes, how can I avoid the other props not getting a empty value?
Thanks
You're passing an object without a password property defined, so it's going to update the state object accordingly.
I'd just loop through the properties of the payload to update each related state object property. And as #82Tuskers pointed out, you'll need to use Vue.set if the property in the payload object doesn't yet exist on the state object (otherwise the property won't be reactive):
authUser: (state, payload) => {
for (prop in payload) {
if (state.hasOwnProperty(prop)) {
state[prop] = payload[prop];
} else {
Vue.set(state, prop, payload[prop]);
}
}
}
This way, only the properties being passed in the payload object will be updated on the state object.
It is not strange, it is expected behaviour. Just rewrite your mutation this (recommended) way:
authUser: (state, payload) => {
state = Object.assign({}, state, payload)
}
While the other answers seem to fix your issue, it might be worthwhile to put the user-related items into a user object inside the state. It is also best practice to establish your state properties up front so you can avoid having to use Vue.set(...):
state: {
user: {
email: '',
password: ''
}
}
...then you could easily avoid looping by using the spread operator: state.user = { ...state.user, ...payload } - this essentially says "take everything currently inside state.user and merge it with payload, overwriting what is in state.user with state.payload"

Categories