How to update a Vuex child key with Object.assign? - vue.js

When payload.key is a key like foo, the code below is working fine, but how to update the value of a child key like foo.bar.a ?
export const mutations = {
USER_UPDATE(state, payload) {
console.log(payload);
state.user = Object.assign({}, state.user, {
[payload.key]: payload.value
});
}
}
=== EDIT ===
This is called by:
computed: {
...mapState(['user']),
fooBarA: {
get() {
return this.$store.state.user.foo.bar.a
},
set(value) {
this.$store.commit('USER_UPDATE', {
key: 'foo.bar.a',
value
})
}
}
}

You are replacing the whole state.user Object reference with a new Object, which destroys reactivity.
This simplified code does not demonstrate the need to use Object.assign, so in this cas you can simply:
export const mutations = {
USER_UPDATE(state, payload) {
state.user[payload.key] = payload.value
}
}
Which keeps the original state.user Object reference.

I have an approach that works.
Where the payload.key is "foo.bar.a"
const mutations = {
UPDATE_USER(state, payload) {
const setter = new Function(
"obj",
"newval",
"obj." + payload.key + " = newval;"
);
let user = { ...state.user };
setter(user, payload.value);
state.user = user;
}
};
Demo
https://codesandbox.io/s/vuex-store-nested-key-setter-x1n00
Inspiration from
https://stackoverflow.com/a/30360979/815507

Related

How to get the cookie value and put it into the Vuex store after refreshing page in vue

I have a product component and I have an I am adding products into cart there:
addToCart: function () {
this.amount = this.itemsCount !== "" ? this.itemsCount : 1;
if(this.variationId != null) {
this.warningMessage = false;
cartHelper.addToCart(this.product.id, this.variationId, parseInt(this.amount), (response) => {
this.$store.dispatch('addProductToCart', {
cart: response.data,
})
});
} else {
this.warningMessage = true;
}
},
And I also have cart helper where I am making my API calls and store the cart_guid in the cookie:
let cartHelper = {
cartCookieName: "_cart",
cookieValue: "",
getCart: function (callback = undefined) {
return apiHelper.getRequest(
"/carts",
(response) => {
document.cookie = `${this.cartCookieName}=${response.data.attributes.cart_guid};`;
this.cookieValue = response.data.attributes.cart_guid;
if (callback) { callback(response); }
}
)
},
addToCart: function (product, variation_id, amount, callback = undefined) {
if(this.cookieValue == "") {
this.getCart(() => {
this._addToCart(product, variation_id, amount, callback);
});
} else {
this._addToCart(product, variation_id, amount, callback)
}
},
_addToCart(product, variation_id, amount, callback = undefined) {
return apiHelper.postRequest(
`/carts/${this.cookieValue}/add-item`,
(response) => {
document.cookie = `${this.cartCookieName}=${response.data.attributes.cart_guid};`;
if (callback) { callback(response); }
},
{
product_id: product,
variation_id: variation_id,
amount: amount,
}
)
},
export default cartHelper;
(I didnt write the code where I am storing the cart_guid in the cookie. I dont think it is necessary, it is basically cookieValue)
So when I add the product into the cart, I am storing this data in Vuex. For this my action:
export const addProductToCart = ({commit}, {cart}) => {
commit('ADD_TO_CART', {cart});
}
my mutation:
export const ADD_TO_CART = (state, {cart}) => {
state.cart = cart;
}
and my state:
export default {
cart: {
"attributes": {
"items": [],
}
}
}
What I am trying to do when I refresh the page, the values in Vuex are lost but since there is still a cookie with the value cart_guid, I should basically make this call and fill the Vuex again with the cart_guid. But I am quite new in Vuex, so I don't know where I should put the logic. I would be really glad if you give me any hint or code.
There is a onMounted lifecycle where the code inside will run whenever the vue component has been successfully mounted onto the DOM. You can put your function where it retrieves the value of your cookie in there so it will after mounted.

strange console.log output with vuex

i have some simple vuex store with
const state = {
todos : []
}
const getters = {
allTodos: (state) => state.todos
}
const actions = {
async fetchTodos({ commit }) {
console.log(this.state.todos)
if(state.todos.length == 0) {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5')
commit('setTodos', response.data)
}
}
}
const mutations = {
setTodos(state, todos) {
state.todos = todos
}
}
why does console.log in fetchTodos action output populated todos before it was populated with axios.get and setTodos mutation?
when i write
const actions = {
fetchTodos({ commit }) {
console.log(this.state.todos)
setTimeout(async () => {
if(state.todos.length == 0) {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5')
commit('setTodos', response.data)
}
}, 10000)
}
}
output is normal with empty todos in state
That's because you will see a little blue triangle right next to the console log. I don't know the technical term for it but what happens is that the browser will update that variable with the current value because it is a reactive variable and since it is a reference being pointed to a location in memory, it will update.
If you truly wish to see the value and prove what was described above, you can write:
console.log(JSON.parse(JSON.stringify(this.state.todos)));

VueJS $set not making new property in array of objects reactive

In my VueJS 2 component below, I can add the imgdata property to each question in the area.questions array. It works - I can see from the console.log that there are questions where imgdata has a value. But despite using $set it still isn't reactive, and the imgdata isn't there in the view! How can I make this reactive?
var componentOptions = {
props: ['area'],
data: function() {
return {
qIndex: 0,
};
},
mounted: function() {
var that = this;
that.init();
},
methods: {
init: function() {
var that = this;
if (that.area.questions.length > 0) {
that.area.questions.forEach(function(q) {
Util.HTTP('GET', '/api/v1/photos/' + q.id + '/qimage').then(function(response) {
var thisIndex = (that.area.questions.findIndex(entry => entry.id === q.id));
var thisQuestion = (that.area.questions.find(entry => entry.id === q.id));
thisQuestion.imgdata = response.data;
that.$set(that.area.questions, thisIndex, thisQuestion);
})
});
}
console.log("area.questions", that.area.questions);
},
Since area is a prop, you should not be attempting to make changes to it within this component.
The general idea is to emit an event for the parent component to listen to in order to update the data passed in.
For example
export default {
name: "ImageLoader",
props: {
area: Object
},
data: () => ({ qIndex: 0 }), // are you actually using this?
mounted () {
this.init()
},
methods: {
async init () {
const questions = await Promise.all(this.area.questions.map(async q => {
const res = await Util.HTTP("GET", `/api/v1/photos/${encodeURIComponent(q.id)}/qimage`)
return {
...q,
imgdata: res.data
}
}))
this.$emit("loaded", questions)
}
}
}
And in the parent
<image-loader :area="area" #loaded="updateAreaQuestions"/>
export default {
data: () => ({
area: {
questions: [/* questions go here */]
}
}),
methods: {
updateAreaQuestions(questions) {
this.area.questions = questions
}
}
}
Here that variable has a value of this but it's bound under the scope of function. So, you can create reactive property in data as below :
data: function() {
return {
qIndex: 0,
questions: []
};
}
Props can't be reactive so use :
that.$set(this.questions, thisIndex, thisQuestion);
And assign your API output to directly questions using this.questions.

Cannot pass multiple arguments in vuex actions

I'm trying to call vuex action in vue component with multiple parameters. But in action method cannot access these passed arguments.
I have already tried passing value in payload as object which is mostly suggested here. but still it is not working.
Please look for
this.getMessageFromServer(payload);
MessageBox.vue
import Vue from 'vue';
import { mapGetters, mapActions } from 'vuex';
import MessageView from './MessageView.vue';
export default Vue.component('message-box',{
components:{
MessageView
},
data() {
return {
messageList :[],
}
},
created() {
this.fetchTimeMessage();
console.log("reaching inside ");
},
computed:{
...mapGetters(['getMessage','getActiveMessageData']),
...mapActions(['getMessageFromServer']),
},
methods: {
fetchTimeMessage:function(){
console.log("fetchTimeMessage : ");
var messageUser = this.getMessage.findIndex((e) => e.muid == this.getActiveMessageData.id);
console.log("fetchTimeMessage : " , {messageUser});
if (messageUser == -1) {
let user_id = this.getActiveMessageData.id;
let user_type = this.getActiveMessageData.type;
console.log("inside fetch Message : " + user_id);
console.log("inside fetch Message : " + user_type);
const payload = {
'uType': user_type,
'uid' : user_id,
'limit': 50
};
this.getMessageFromServer(payload);
}
},
},
});
Vuex modules message.js
const state = {
messages:[],
activeMessage : {}
};
const getters = {
getActiveUserId: (state) => {
let activeUserId = "";
if (!utils.isEmpty(state.activeMessage)) {
activeUserId = state.activeMessage.id;
}
return activeUserId;
},
getActiveMessage:(state) => { return !utils.isEmpty(state.activeMessage);},
getActiveMessageData : (state) => {return state.activeMessage } ,
getMessage: (state) => {return state.messages},
};
const actions = {
getMessageFromServer({ commit, state },{utype,uid,limit}){
console.log("mesage callback asdas : " + uid);
let messageRequest = CCManager.messageRequestBuilder(utype, uid, limit);
messageRequest.fetchPrevious().then(messages => {
//console.log("mesage callback : " + JSON.stringify(messages));
// handle list of messages received
let payload = {
'messsages':messages,
'id': uid
};
console.log("inside action_view : " + JSON.stringify(payload));
//commit('updateMessageList',payload);
})
},
setActiveMessages:function({commit},data){
commit('updateActiveMessage',data);
},
};
const mutations = {
updateMessageList(state,{messages,id}){
console.log("action details" + id);
//uid is not present
var tempObj = {
'muid' : id,
'message' : messages
}
state.messages.push(tempObj);
}
},
updateActiveMessage(state,action){
state.activeMessage = {
type: action.type,
id: action.uid
};
}
};
export default {
state,
getters,
actions,
mutations
};
Change the way you call the action in your component:
this.$store.dispatch('getMessageFromServer', payload);
And pass the payload as a single object in your action function:
getMessageFromServer({ commit, state }, payload)
And you can then access the payload properties in the action like this:
getMessageFromServer({ commit, state }, payload) {
var uid = payload.uid;
var uType = payload.uType;
var limit = payload.limit;
}

When an object's state changes another object's state changes also in react native redux

I am trying to hold user info with defaultUser as default state after fetching. But If user state changes with UPDATEUSERSTATE, defaultUser also changes. I could not understand that behaivour
Firstly fetching the data from restApi
Updating user state on MainComponent
If User changes textinput on ModalView, updating the user state.
const userReducer = (state = {} ,action) => {
switch (action.type) {
case actionTypes.GETUSERINFOBYUSERNAME_SUCCESS:
return {
...state,
isFetching: action.isFetching,
error: action.error,
user: action.user,
defaultUser:action.user,
open: true
};
case actionTypes.UPDATEUSERSTATE:
return {
...state,
user: action.user
}
default:
console.log("[userReducer]: defalt state");
return state;
}
};
//ACTIONS
export const getUserInfoByUserNameSuccess=(user) => {
return {type: actionTypes.GETUSERINFOBYUSERNAME_SUCCESS, isFetching: true, error: null, user: user}
}
export const updateUserState=(user) => {
return {type: actionTypes.UPDATEUSERSTATE, user:user}
}
//CALLING GETUSERINFO
this.props.onGetUserInfoByUserName(val);
const mapDispatchToProps = dispatch => {
return{
onGetUserInfoByUserName : userName => dispatch(getUserInfoByUserNameFetching(userName))
};
};
//AND CALLING UPDATEUSERSTATE
textChangedHandler = (key,value) => {
let user = this.props.user;
user[key]=value;
this.props.onUpdateUserState(user);
}
const mapDispatchToProps = dispatch => {
return{
onUpdateUserState : (user) => dispatch(updateUserState(user))
};
};
The problem here is that you're directly setting the values of two separate objects to reference the same object (both user and defaultUser are being set to reference object action.user). So if you change the value of one of the objects, the reference changes which changes the second object.
Redux doesn't replace the object with a new one, but rather does a shallow copy of the new values. See the below snippet for an example:
var actionUser = { foo: 1 }
var defaultUser = actionUser
var user = actionUser
user.bar = 2
console.log(actionUser)
// { foo: 1, bar: 2 }
console.log(defaultUser)
// { foo: 1, bar: 2 }
console.log(user)
// { foo: 1, bar: 2 }
To fix this you can use the Object.assign() method to assign the references to a new object. See below snippet:
var actionUser = { foo: 1 }
var defaultUser = Object.assign({}, actionUser)
var user = Object.assign({}, actionUser)
user.bar = 2
console.log(actionUser)
// { foo: 1 }
console.log(defaultUser)
// { foo: 1 }
console.log(user)
// { foo: 1, bar: 2 }
So whenever you assign a new value from your actions to any of your state objects, use Object.assign().
Example (from your code):
case actionTypes.GETUSERINFOBYUSERNAME_SUCCESS:
return {
...state,
user: Object.assign({}, action.user),
defaultUser: Object.assign({}, action.user),
};
case actionTypes.UPDATEUSERSTATE:
return {
...state,
user: Object.assign({}, action.user),
}