Updating multiple values in redux - javascript

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.

Related

How to reuse Redux reducer logic for multiple instances of the same data type?

Say I have an application for keeping track of university courses. Each course has some attributes like name time notes id. In the application, the user can add/remove courses and edit their attributes. How would I structure a redux store for this?
My first thought is to make the state a mapped object like so:
interface Course {
id: string;
name: string;
time: string;
notes: string;
}
interface StoreState {
[id: string]: Course;
}
where each entry in the state corresponds to a course instance. Then, when the user adds a new course, I add a new entry to the state
const slice = createSlice({
name: 'courses',
initialState: {},
reducers: {
addCourse: (state, action) => {
state[action.payload.id] = {
id: action.payload.id,
name: action.payload.name,
...
...
}
},
updateNotes: (state, action) => {
state[action.payload.id].notes = action.payload.notes
}
...
...
}
})
This is not ideal because I have to redundantly use the course id in every case reducer to update the correct course. I'd like to have a state object in the store for each course and write my case reducers like this:
updateNotes: (state, action) => {
state.notes = action.payload.notes
}
I shouldn't have to worry about which course I'm updating because they all function the same way. Is there a way to dynamically create states for each course that share the same reducer logic?
You need to implement a dynamic version of redux's combineReducers().
There seems to be a package that does this: https://www.npmjs.com/package/redux-injector
Looking at this I think what you concerned about is ending up with a data structure where ID is both a key and a property in your stored data and this breaks the idea that the data store in Redux should be the minimum dataset without any repetition.
The solution to this is to only store the ID as the key and the use a library called Reselect to merge the key back into the data when you take it from the store.
https://github.com/reduxjs/reselect

How to stop reducer key being a key in itself

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.

Vuejs - Vuex behavior when committing a property

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"

Redux reducer to fetch, like, and dislike posts

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

call to mapStateToProps appears to pass wrong state object

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.

Categories