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
Related
I am trying to update a property list in Vue.js using Inertia.js:
props: {
list: {
type: Object,
default: {}
}
},
updateTable(filters) {
axios.post(route('updateList'), filters)
.then(r => {
this.list = r.data
})
}
But I get the following error: TypeError: 'set' on proxy: trap returned falsish for property
In Inertia.js, all props are provided as proxies. As described in this mdn article, the proxie's set method needs to return true to allow the assignment. However, I have no idea how to correctly achieve that as I do not create the proxy myself. Any suggestions?
Or is it that with Inertia I would always be forced to use a partial reload?
I believe there's some misunderstandings of Vue itself and how Inertia works here.
You're receiving the list as a prop. Props should not be directly changed.
If you ABSOLUTELY need to change it, you can reference it directly by this.$page.props.list instead of receiving the list as a prop.
Or, you can do this:
export default {
props: {
list: {
type: Object,
default: {}
}
},
data () {
return {
listCopy: this.list
}
},
mounted () {
// Handle your scroll events
axios.get(this.listCopy.next_page_url).then(response => {
this.listCopy = {
...response.data,
data: [...this.listCopy.data, ...response.data.data]
}
})
}
}
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
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;
}, {});
}
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')
}
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?