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
Related
I am using combineReducer to combine reducers and reducer like this
const todo = (state = {}, action) => {
switch (action.type) {
//...
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
return Object.assign({}, state, {
completed: !state.completed
})
default:
return state
}
}
My problem is if i am defining reducer like that i am getting sonar code smell
Function parameters with default values should be last1
but combine reducer pass argument in this sequence only how to work on this?
we did have the same issue within our project, and sonar allows you to define exclusions for rules and files in Administration -> Congifuration -> Analysis Scope.
you will find there a section called Ignore issues on Multiple Criteria and there you can enter the rule and a "file pattern" to exclude files from this rule.
like:
From the Sonarqube docs:
Function parameters with default values should be last:
...But all function parameters with default values should be declared
after the function parameters without default values. Otherwise, it
makes it impossible for callers to take advantage of defaults; they
must re-specify the defaulted values or pass undefined in order to
"get to" the non-default parameters.
This does, however, work with Redux as it calls your reducer for the first time with undefined as the first argument. If you want to continue using this pattern, you'll need to disable the rule or skip that line from analysis.
What if you define a default value for the second arg?
const todo = (state = {}, action = null/undefined) => {
Set action with {}:
for example, changing the below code
const todo = (state = {}, action) => { }
to this
const todo = (state = {}, action = {}) => { }
Will be the safest way.
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'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"
I am working on my first React-Redux app.
This is the code of my reducer (I only have one):
const CalculationsReducers = (state = {}, action) => {
switch (action.type) {
case CalculationsActions.LOAD_CALCULATIONS:
return Object.assign({}, state, {
calculations: [{
id: 'abc',
name: 'test',
date: 'test',
status: 'in progress'
}]
});
default:
return state;
}
};
This is the code of my 'mapStateToProps' function I use with connect()
const mapStateToProps = (s) => {
return {
calculations: s.calculations ||[]
};
};
When I dispatch an action with type LOAD_CALCULATIONS, I can see a log trace (using react-logger), but the state object seems very strange to me. Can someone point me my error?
log trace using react-logger
The workflow looks good to me. The state object also looks fine, but you might want to change the alias when importing reducer to configure your store, to make it more intuitive.
In your configure store file:
import calculations from 'reducers/CalculationReducer';
Note that it requires you to export CalculationReducer as default. Then you will see calculations instead of CalculationReducer in your state object.
And I noticed you have a payload along with your action but never got handled in reducer. If you need it in state object then handle it in reducer using action.payload.