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?
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 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
.
Assume data has already been cached in sessionStorage. I have hydrateStateWithSessionStorage in an external CacheService.js file. I import this file. When I try to pass this.setState to this function, I get this error:
Uncaught TypeError: Cannot read property 'updater' of undefined
How can I solve this? I could possibly use a the React hook useState and pass the setter function, but what if I want to use a class component instead of functional component? Or am I simply unable to pass setState because it implicitly uses the 'this' keyword in its implementation?
hydrateStateWithSessionStorage(state, setState) {
// for all items in state
for (let key in state) {
// if the key exists in localStorage
if (sessionStorage.hasOwnProperty(key)) {
// get the key's value from localStorage
let value = sessionStorage.getItem(key);
// console.log(value)
// parse the localStorage string and setState
try {
value = JSON.parse(value);
console.log('before')
setState({ [key]: value });
console.log('after')
} catch (e) {
// handle empty string
setState({ [key]: value });
}
}
}
}
//in the component consuming CacheService
//this.Cache = new CacheService(); //in the constructor
componentDidMount() {
this.Cache.hydrateStateWithLocalStorage(this.state, this.setState);
this.Auth.fetch('api/upcomingbill/').then((data) => {
this.setState({ list: data })
});
}
I would treat this function more of a check return sessionStorage object if nothing return undefined. Send this.state into the fn() then check response and return the response.
componentDidMount() {
const newState = hydrateState(this.state):
!!newState && this.setState(newState)
}
Just a brain dump..
How about this?
componentDidMount() {
this.Cache.hydrateStateWithLocalStorage(this);
this.Auth.fetch('api/upcomingbill/').then((data) => {
this.setState({ list: data })
});
}
And then using setState like so...
hydrateStateWithSessionStorage(ReactComp) {
for (let key in ReactComp.state) {
...
ReactComp.setState({ [key]: value });
....
}
}
FYI, this is not a React specific issue. The reason you are getting that error is, you are passing just a method that internally uses "this" (of that particular class) to the hydrateStateWithSessionStorage function. By the time, that method is called in the function, the scope has changed and "this" is not defined or not the same anymore.
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"]
I'm trying to save the application's root state on mounted lifecycle of VueJS and freeze the copy in $root's data, and my attempt is as following,
mounted() {
this.globalState = this.$store.state;
},
data () {
return {
globalState: null
}
}
However this approach is updating globalState so I came up with another way to freeze it by using Object.freeze() but with no luck it keeps updating.
Also I've tried to copy the this.$store.state to a const variable and update globalState via it, but it also fails.
My last attempt is as following, I know it's an ugly code but please bear with it.
let emptyObj = {};
Object.defineProperty(emptyObj, 'globalState',{
value: this.$store.state,
enumerable: false,
configurable: false,
writable: false
}
);
this.globalState = emptyObj.globalState;
My question is, how can I copy and freeze the initial state of the application and store it in single data?
Since you do not want reactivity on the copy of the state object its better to create a custom option in your components option instead of freezing the object in the data option.
import {cloneDeep} from "lodash"
export default{
myGlobalState: null,
mounted() {
this.$options.myGlobalState = cloneDeep(this.$store.state);
}
}
Your custom option can be accesed using vm.$options and is not reactive
this.$options.myGlobalState
You can do that by using a computed value which has an empty setter.
e.g.
import _ from 'lodash';
{
data() { return {} },
computed: {
globalState: {
get() {
return _.cloneDeep(this.$store.state);
},
set(input) {}
}
},
mounted(){
this.globalState = {test2:456};
// this won't change anything
}
}
https://codepen.io/jacobgoh101/pen/bvgMRR?editors=1111