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
.
Related
I just wonder if I could do the code below less ugly.
In the component I have a property person. I'd like to use fields of the person object in my template without prepending each field with person.something. But the only way I know is below.
This is what I have atm:
(Please consider the code below as just an example, it's not a real one)
{
name: 'Demo',
props: {
person: {
type: Object,
default: {}
}
},
computed: {
firstName() {
return this.person.firstName
},
lastName() {
return this.person.lastName
},
age() {
return this.person.age
},
gender() {
return this.person.gender
}
}
}
This is what I want to have instead (kind of):
{
name: 'Demo',
props: {
person: {
type: Object,
default: {}
}
},
computed: {
...this.person // <-- something like this would be better if only it would work
}
}
Some assumptions
I assume that things like this should be possible, because we have mapGetters of vuex:
computed: {
...mapGetters({ something: SOMETHING })
},
With vue 3 or the composition api plugin for vue 2 you could use toRefs to spread the prop value inside the setup hook :
import {toRefs} from 'vue'//vue3
//import {toRefs} from '#vue/composition-api'//vue 2
export default{
name: 'Demo',
props: {
person: {
type: Object,
default: {}
}
},
setup(props){
return {...toRefs(props.person)}
}
}
I see your point but what you want is not possible.
The main problem is this. We work with Options API. What is computed? An object that is passed into a Vue and Vue creates new instance with computed property for each function (or get/set pair) inside computed object. That means the spread operator is executed at the time component instance does not exist yet which means there is no this
mapGetters works because it's input are just static strings. If you had some static description of the Person object - for example some schema generated from Open API specification - you could create mapProperties helper and use it to generate computed props...
Edit:
Yes, there is a way to create computed props dynamically in beforeCreate by modifying $options object - at least it was possible in Vue 2. Not sure about Vue 3. In both cases it is documented to be read only and Vue 3 is somehow more strict in forcing "read onlyness". However this is very different approach from the one in your question...
The approach is demonstrated for example here
I'm trying to create an asynchronous produce with immer but I get an error when I call this async function :
This my code :
import { combineReducers, createStore } from 'redux';
import produce from 'immer';
const mainReducer = produce(async (draft, { type, payload }: { type: string; payload: any }) => {
switch (type) {
case 'foo': {
draft = await myAsyncFn(payload);
}
}
});
const reducers = {
main: mainReducer,
};
const rootReducer = combineReducers(reducers);
export const mainStore = createStore(rootReducer);
This is the output : Error: [Immer] produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '[object Promise]'
Why doesn't it work ? (I thought It's was possible : https://immerjs.github.io/immer/docs/async)
What does it mean by classes that are marked with '[immerable] ?
While Immer does appear to let you write async logic inside of produce, you must never write async logic in a Redux reducer.
Having said that, the specific error here is because by default, Immer only knows how to update plain JS objects and arrays. In order to update a class instance or similar, you have to add a special immerable symbol to that class type.
Note that you should be using Immer with Redux, but as part of our official Redux Toolkit package, which has Immer built into its createSlice API.
Might not be so relevant but I got this error using redux-toolkit. The createSlice() method was working well until I changed the type of the state to a generic typescript type. Then it started throwing the mentioned error.
import { AsyncState } from "./AsyncState";
export class SliceState<T> {
constructor(initialState: T) {
this.data = initialState;
this.status = new AsyncState();
}
status: AsyncState;
data: T;
}
Additional error information: [Immer] produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '[object Object]'
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;
}, {});
}
I'm trying to set a data property (in Classroom) based on what's in the store (of Lesson). But I keep getting undefined. How can I get that value and set it in data?
Classroom.vue:
data(){
return {
selectedOption: this.currentLesson
}
}
computed: Object.assign({},
mapGetters({
currentLesson: "lesson/current"
})
)
lesson.js:
const state = {
current: {}
}
const getters = {
current(){
return state.current
},
}
index.js:
export default new Vuex.Store({
modules: {
lesson,
user
}
})
UPDATE:
The component's data function is called before the computed values are set up. So you cannot use computed properties inside data function. (That is reasonable, because some computed getters might rely on certain properties from data. It might cause infinite loops if we set up computed values before calling data).
In your case, if you want selectedOption to always be the same as currentLesson, then you don't need to put it in the component's local data, just use this.currentLesson directly in your view template.
However, if you just want to set up an initial value for selectedOption based on lesson/current, you can explicitly access it via:
selectedOption: this.$store.getters['lesson/current']
Or by using a lifecycle hook like created or mounted:
created () {
this.selectedOption = this.$store.getters['lesson/current']
// or `this.selectedOption = this.currentLesson` if you keep that mapped getter
}
Original answer:
currentLesson: "lesson/current" is the "namespaced getter" syntax. You need to set namespaced: true for your lesson store definition. Did you set that field when you export lesson.js?
I'm looking to format a date fetched via Apollo-GraphQL using the javascript Moment library. I'm using VueJS Apollo on the client side to make graphQL queries as such:
import { ALL_EVENTS } from '../constants/graphql.js'
import Event from './Event'
import moment from 'moment'
export default {
name: 'EventList',
data: () => ({
events: [],
loading: 0
}),
apollo: {
events: ALL_EVENTS
},
components: {
Event
},
The apollo middleware returns a list of objects that contain an id, name, a startDate (UTC formatted string)
, and an endDate (also a UTC formatted string) property, among other properties added by apollo for it's use.
Whenever I try to make a computed property in VueJS using the object list from Apollo, it gives me a read-only error, but it seems to me like I'm creating a new object:
computed: {
eventsFormatted: function () {
var out = this.events
for (var i in out) {
out[i].startDate = moment(out[i].startDate)
out[i].endDate = moment(out[i].endDate)
}
return out
}
}
What do I need to do to make a duplicate of the array that I can modify?
When you do var out = this.events the out var is now pointing to the same array of objects as this.events.
So if this.events is read-only, you'll need to make a copy of the array and if each object in the array is read-only you'll need to make a copy of each object as well.
Here's an example using the spread operator to copy the array and objects:
computed: {
eventsFormatted() {
let out = [...this.events];
for (let i in out) {
let item = {...out[i]};
item.startDate = moment(item.startDate);
item.endDate = moment(item.endDate);
out[i] = item;
}
return out;
}
}
The above makes a shallow copy of each object in the array. If you need to make a deep copy because of the structure of this.events, see this post.