Making read-only fields in a Vue observable? - vue.js

I'm exposing a Vue observable that reflects sign-in status:
// auth.js
const user = Vue.observable({
displayName: '...',
isSignedIn: null // Boolean
});
...
export { user };
Though the name is observable, I think anyone importing user can also modify the fields. How could I prevent this?

You can freeze the object before make it reactive, like this example below.
const user = Vue.observable(
Object.freeze({
displayName: '...',
isSignedIn: true // Boolean
})
);
The question is why to use this, better to expose a non-reactive object. Check the Vue.observable definition here

Related

Vue component not showing updated Vuex data

I realise this is a common issue with people new to vue and vuex, but I've been using it for two years now and thought I understood the ins and outs. Yet I'm stumped. There must be something I'm overlooking.
We've got a couple of complex models that used to have layouts hard-coded in the front end, and now some of those come from the backend instead, so I added a store module to handle that:
import { ActionTree } from 'vuex';
import { RootState } from '#/store';
import request from '../../services/request';
import layouts from '../../layouts';
export const types = {
FETCH_LAYOUT: `${MODULE_NAME}${FETCH_LAYOUT}`,
};
const initialState = {
layout: layouts,
};
const actions: ActionTree<LayoutState, RootState> = {
async [FETCH_LAYOUT]({ commit, state }, id) {
if (!state[id]) {
const layout = await request.get(`layout/${id}`);
commit(types.FETCH_LAYOUT, { layout, id });
}
},
};
const mutations = {
[types.FETCH_LAYOUT](state: any, { layout, id }) {
state.layout[id] = layout;
},
};
export default {
namespaced: true,
state: initialState,
getters: {},
actions,
mutations,
};
Everything here seems to work fine: the request goes out, response comes back, state is updated. I've verified that that works. I've got two components using this, one of them the parent of the other (and there's a lot of instances of the child). They're far to big to copy them here, but the import part is simply this:
computed: {
...mapState({
layout: (state: any) => {
console.log('mapState: ', state.layout, state.layout.layout[state.modelName]);
return state.layout.layout[state.modelName];
},
}),
},
This console.log doesn't trigger. Or actually it looks like it does trigger in the parent, but not in the children. If I change anything in the front-end code and it automatically loads those changes, the child components do have the correct layout, which makes sense because it's already in the store when the components are rerendered. But doing a reload of the page, they lose it again, because the components render before the layout returns, and they somehow don't update.
I'm baffled why this doesn't work, especially since it does seem to work in the parent. Both use mapState in the same way. Both instantiate the component with Vue.component(name, definition). I suppose I could pass the layout down as a parameter, but I'd rather not because it's global data, and I want to understand how this can fail. I've considered if maybe the state.layout[id] = layout might not trigger an automatic update, but it looks like it should, and the parent component does receive the update. I originally had state[id] = layout, which also didn't work.
Using Vue 2.6.11 and Vuex 3.3.0

How to empty a Vuex store module state object?

How to empty a Vuex store module state object completely? I know how to delete a single property of state object for e.g Vue.delete(state.slidesList, 'slide1') but I want to completely empty the state object (not delete the object itself) without losing reactivity on the object itself, when individual properties are deleted (using Vue.delete) removes reactive getters and setters, I believe, pls correct if this is wrong.
Does directly setting state.slideList = {} empties the object, while still being reactive? if yes then it implies state.slideList = {fname: 'Appu' } is a way to overwrite the object without emptying the object (if the goal is to overwrite the object completely with another object, rather than empty and re-write in it).
If not, what is the correct way to overwrite the state object with another new non-empty object.
Thanks
set adds a property to a reactive object, ensuring the new property is also reactive, so triggers view updates. This must be used to add
new properties to reactive objects, as Vue cannot detect normal
property additions. doc
module.js
import Vue from 'vue'
const initialState = {
foo: {},
bar: {},
// ...
}
const state = {...initialState}
const mutations = { // To reset a state
RESET_FOO (state) {
Vue.set(state, 'foo', initialState.foo)
},
UPDATE_FOO (state, payload) { // To update the state
Vue.set(state, 'foo', payload)
},
UPDATE_IN_FOO (state, { key, value }) { // To update a key in a state
Vue.set(state.foo, key, value)
},
RESET_MODULE (state) { // To reset the entire module
Object.assign(state, initialState)
}
}
Vuex has replaceState method that replaces store's root state.
Create a mutation that resets the state. Then, reset the state with a default value like this:
Object.assign(state, newState)

Computed Property does not get updated when state changes

We are trying to detect whether a person is logged in or not using the vuex store state: loggedIn. When I call the API service from the action it calls the mutation after successful login and changes the data in the state:
loginSuccess(state, accessToken) {
state.accessToken = accessToken;
state.authenticating = false;
state.loggedIn = true;
console.log(state.loggedIn);
}
The console.log() shows the value, so the mutation is working.
In my other component, I use a computed property to watch for changes in the store using ...mapState() and bound the property in the template view:
computed: {
...mapState('authStore',['loggedIn' ]);
}
But the view never gets updated based on the computed property. I checked using the Vue dev tools in the console. It shows the state changes.
I have initialized the state.
export const states = {
loggedIn: false
};
I have tried to call the state directly.
this.$store.state.authStore.loggedIn;
I have tried different approaches.
...mapState('authStore', { logging:'loggedIn' });
//or
...mapState('authStore',['loggedIn' ]);
also, tried watch: {} hook but not working.
Interestingly though, the state's getter always shows undefined, but the state property changes in the dev tools.
Cannot figure out what is wrong or how to move further.
here is the screenshot of devtools state after successful login:
This catches my eye:
export const states = {
loggedIn: false
};
My suspicion is that you're then trying to use it something like this:
const store = {
states,
mutations,
actions,
getters
}
This won't work because it needs to be called state and not states. The result will be that loggedIn is unreactive and has an initial value of undefined. Any computed properties, including the store's getter, will not be refreshed when the value changes.
Whether my theory is right or not, I suggest adding console.log(state.loggedIn); to the beginning of loginSucess to confirm the state prior to the mutation.

How can I directly set value in state of VueX

I just want to change data in state of VueX without pass value through following step Action > Mutation > State then getData from state of VueX in other component, Is it possible to do or anyone has another best way to do send value with array to ...mapAction please explain me,
Actually, I just want to send data with array to other component which the data will be change every time when user selected checkbox on Treevue component that I used it.
Thank a lot.
## FilterList.vue ##
export default {
data() {
return {
listSelected: ['aa','bb','cc','...'], // this value will mutate when user has selected checkbox
}
}
}
=================================================================
## store.js ##
export default new Vuex.Store({
state = {
dataSelected: [ ]
},
mutation = {
FILTERSELECTED(state, payload) {
state.selected = payload
}
},
action = {
hasSelected(context,param) {
context.commit('FILTERSELECTED',param)
}
},
getters = {
getSelected: state => state.dataSelected,
}
strict: true
})
You can set strict: false and change data directly, but I wouldn't recommend it.
You'll lose the benefit Vuex provides, i'd rather share that object outside vuex.
Not every change needs to be synced with the store, it depends on the scenario.
For a EditUser component as example, I'll start with a deep copy of the user object from the store:
this.tmpUser = JSON.parse(JSON.stringify(this.$store.state.user))
This tmpUser is disconnected from the store and won't generate warnings (or updates) when you change its properties.
When the user presses the "save" button, i'll send the changed object back to the store:
this.$store.dispatch("user/save", this.tmpUser)
Which updated the instance in the store and allows the other parts of the application to see the changes.
I also only write actions when async (fetching/saving data) is needed.
For the sync operations I only write the mutations and the use the mapMutations helper or call $store.commit("mutation") directly.

Watching and tracking state changes with Vuex inside child components

This is more a "which method should I use" question, rather than a how to.
I have the following in my Vuex.Store() instance:
store.js:
export default new Vuex.Store({
state: {
acceptedTermsAndConditions: false
},
})
From various components I'm emitting an event which sets this.$store.state.acceptedTermsAndConditions to true or false, dependent on different User inputs.
However, in my component I would set the checked value of a "Accepts T&Cs" checkbox to this value, something like this:
components/Component.Vue:
data () {
return {
form: {
checkboxTermsAndConditions: this.$store.state.acceptedTermsAndConditions
}
}
}
I'm just not sure what method handles this? Does a solution require a getter? If not, what is the best way to watch for state changes and set data values accordingly?
If you want to set the checkbox state based on the stored value, you should use the computed object and the mapGetters helper function in your component:
https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper
computed: {
...mapGetters(['acceptedTermsAndConditions'])
}
Like this, the value will be accessible in your component. If you want to do the contrary (refresh the store based on the checkbox value), you should create a mutation in your store and you should use this in your component:
methods: {
...mapMutations(['setTACcheckbox'])
}
This way, inside your component you can refresh the store value with this.setTACcheckbox(value).