I'm noticing a weird behavior in Vuex when I try to mutate a property in the state obj.
Example:
Mutation: {
authUser: (state, payload) => {
state.email = payload.email
state.password = payload.password
...someOtherProps
}
actions: {
commit ('authUser', {
email: 'user#gmail.com'
})
}
What I noticed is that when I commit only one property(in this case "email"), the value of all other properties of the authUser will be undefined and only email value will be available.
Is that the way Vuex behaves in this case? If yes, how can I avoid the other props not getting a empty value?
Thanks
You're passing an object without a password property defined, so it's going to update the state object accordingly.
I'd just loop through the properties of the payload to update each related state object property. And as #82Tuskers pointed out, you'll need to use Vue.set if the property in the payload object doesn't yet exist on the state object (otherwise the property won't be reactive):
authUser: (state, payload) => {
for (prop in payload) {
if (state.hasOwnProperty(prop)) {
state[prop] = payload[prop];
} else {
Vue.set(state, prop, payload[prop]);
}
}
}
This way, only the properties being passed in the payload object will be updated on the state object.
It is not strange, it is expected behaviour. Just rewrite your mutation this (recommended) way:
authUser: (state, payload) => {
state = Object.assign({}, state, payload)
}
While the other answers seem to fix your issue, it might be worthwhile to put the user-related items into a user object inside the state. It is also best practice to establish your state properties up front so you can avoid having to use Vue.set(...):
state: {
user: {
email: '',
password: ''
}
}
...then you could easily avoid looping by using the spread operator: state.user = { ...state.user, ...payload } - this essentially says "take everything currently inside state.user and merge it with payload, overwriting what is in state.user with state.payload"
Related
I have a namespaced Vuex mutation and I am trying to update multiple state properties at once. In order to avoid repetition I would like to use the ES6 spread operator. Object.assign works as expected but the spread operator fails to update the VUEX state. Here is the code which illustrates the problem:
const authorizationModule = {
namespaced: true,
state: {
status: '',
jwt: '',
user: {
email: ''
}
},
mutations: {
AUTH_SUCCESS(state, payload) {
const { jwt, user } = payload;
// Too much repitition (needs to be abstracted)
state.jwt = jwt;
state.status = 'status';
state.user = user;
// 1. Object.assign works correctly
// state = Object.assign(state, {
// jwt,
// status: 'success',
// user
// });
// 2. Spread operator does not work
// let newState = {
// ...state,
// jwt,
// status: 'success',
// user
// }
// state = newState
},
}
}
It's interesting because I threw a debugger into the code and inspected it in the browser. It seems like the return value for the spread operator is returning a different object. The Object.assign implementation seems to have getter and setter functions in the object (which is the reason it is working as intended). Here is an image of my console:
You can see that the object in the 2nd example is missing certain Vuex getter and setting methods.
So the question remains: is it possible to use the spread operator in this context to update multiple state properties in Vuex?
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 update a nested object in redux but I'm not quite sure if I'm doing it correctly. The code provided below doesn't compile as there's a syntax error in the reducer.
I understand it's not the right syntax as there's no key before action.payload but I'm lost in terms of how I merge the action.payload into the [action.id].
I'm trying to avoid creating individual actions for every key I need to update (e.g. updateBlockType, updateBlockTitle, updateBlockDescription, etc.)
Dispatch:
this.props.dispatch(updateBlock(this.props.id, { type: e.currentTarget.id }))
Action:
export function updateBlock (id, values) {
return {
type: 'UPDATE_BLOCK',
id: id,
payload: values
}
}
Reducer:
case 'UPDATE_BLOCK':
return {
...state,
blocks: {
...state.blocks,
byHash: {
...state.blocks.byHash,
[action.id]: {
...state.blocks.byHash[action.id],
action.payload <-- How do I merge the payload?
}
}
}
}
byHash[id] object:
'1': { id: 1, type: 'INTRO', title: 'Some title...', description: 'Some description...' }
My way of structuring my reducer might be wrong, so I'm open to how else I should tackle this.
You missed the spread operator : ...action.payload which will spread the properties of the payload which is an object to the object entry in reducer
If the structure gets more complex, it is better to compose reducers to sub-reducers and merge them with thr combineReducers api from redux.
Also you are accessing state.blocks and state.blocks.hash in the reducer. This might throw an error if initial state does not define these keys. That is one more reason to compose complex reducers.
basically I always have this problem whereby as reducers are keys of the object store
they are accessed like this.props.users for examples
so if users state is an array, this is fine
const initialState = []
but if I have this:
const initialState = {
users: [],
loading: false,
error: ""
};
coz you know, my reducer needs to do a few more things
then suddenly I have this horrible thing all over my codebase where I'm doing things like:
this.props.users.users clearly, disgusting. how can I get it back to this.props.users but then able to access the other stuff as well?
one example of reducer code case:
case FETCHING_USERS_SUCCEEDED: {
return {
...state,
loading: false,
users: [].concat(...state.users).concat(action.userData)
};
}
you can declare user as a variable like this
const {users} = this.props.users
thereafter you can use users instead of this.props.users.users
When you map your state to props, map state.users.users instead of just state.users.
I have made a like functionality with Redux and React Native.
I have a reducer with initial state posts = [] and my reducer looks like
const initialState = {
posts: []
};
function postReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
posts: action.posts
};
case LIKE_POST:
return {
...state,
posts: state.posts.map(post => {
if (post._id === action.postId) {
return {
...post,
likes: !!post.likes ? post.likes.concat(action.userId) : [action.userId]
}
}
return post;
})
};
case UNLIKE_POST:
return {
...state,
posts: state.posts.map(post => {
if (post._id === action.postId) {
return {
...post,
likes: post.likes.filter(userId => userId !== action.userId)
}
}
return post;
})
};
default:
return state
}
};
I know that I can not mutate the posts array in my state, so I have to return a new array of posts where I have modified the post that a user tries to like/unlike.
It seems to work very well, but it's daunting slow. I only have very few posts, but I still have to wait almost a second for it the like to be visible.
Is this the right approach? Am I storing my posts correctly as a simple array in the state? I'm not sure what the alternative is, but I have, for instance, seen in this GitHub repo that it can be done different, although I don't fully understand the structure of it.
You are right that this is slow and tedious, but it is necessary if you want to ensure you don't mutate state and stick to the correct patterns that are suggested.
That said you have a few options, an obvious one is to make helper functions that solve the common problems you face, however a (personally) better solution is to use ImmutableJS. It will provide you with three things:
The first is it guarantees you aren't mutating state as all changes return a new and different copy.
The second is a convenient API that will allow you to do your mutations with ease instead of using awkward syntax to work around vanilla javascript.
The third is more of a minor benefit but you get access to Immutable types such as List, Map, a variety of Collections and define your own types with Records, which I recomment to use for each reducer state as you can declare the shape of the state, and only mutate the defined properties, instead of adding new ones.
With Immutable a single part of your reducer would look like this:
case FETCH_POSTS:
return state.set('posts', action.posts);
case LIKE_POST:
let posts = state.posts.map( (p) => {
if (p.id === action.postId) {
return p.set('likes', 'something');
}
return p;
}
It gets even more exciting if you build your state off a Record, so you would have:
export default Record({
posts: List(),
}, 'SocialRecord');
Which would then let you have state.get('posts') or state.posts and still have immutable functionality as Records have getters.
Check out the docs for more information:
Immutable Documentation