A better way to handle updating state in reducer - reactjs

So I'm trying to learn react-redux and in my reducer GET_COMMENTS. I need to check first if there is already items in state.comments so I try to use if-else statement and it works. But maybe there's still a better way to handle it ?
case 'GET_COMMENTS':
let list = []
if (state.comments.length > 0) { // check if there's an item
list = state.comments // pass existing state
action.payload.data.map(comment => { // map through new data
list = [...list, comment] // dont know if this is the right way but it works
})
} else {
list = action.payload.data // if state.comments is empty directly pass new data
}
return {
...state,
comments: list,
next: action.payload.next
}
UPDATE: I decided to go with Gabriele answer as I think its the best approach. And Today I learn that .concat() method is used to join two or more arrays. This method does not change the existing arrays, but returns a new array, containing the values of the joined arrays.

I would just do
case 'GET_COMMENTS':
return ({
...state,
comments: state.comments.concat(action.payload.data),
next: action.payload.next
});

Yes it is correct. I would simplify your approach to
...
case 'GET_COMMENTS':
return {
...state,
comments: [...state.comments, ...action.payload.data]
next: action.payload.next
};
Note: I consider that action.payload.comments is a new array of comments. And initial state is { comments: [] }.

Related

Redux: will this be the same state after I change a property of an element in state?

I am working on the reducer file which looks like this:
export default (state = [], action) => {
let index
switch (action.type){
case "ADD_QUOTE":
return [...state,action.quote]
case "REMOVE_QUOTE":
return state.filter(q=>q.id !==action.quoteId)
case "UPVOTE_QUOTE":
index = state.findIndex(quote => quote.id === action.quoteId);
let quote = state[index];
return [
...state.slice(0, index),
Object.assign({}, quote, { votes: quote.votes += 1 }),
...state.slice(index + 1)
];
default:
return state
}
}
I have two ways to implement case UPVOTE_QUOTE, the first one is in the above code, and the second one looks like this:
...
case "UPVOTE_QUOTE":
let quote = state.find(q=>q.id===action.quoteId)
quote.votes +=1
return state
...
I wonder what is the difference. And in the second situation, what I am not sure is, if I change the votes property in that one quote element in state, when I return the state, will it be the state with the new quote? And is this state the original state or a new state that refers to a new place? Very confused...
The first option looks to be correct. The second option would be mutating the state.
These links that may help explain further: Mutating Redux State Consequences and Why cant state be mutated....

How to update nested object in redux

I have an object:
{ //birthdaysObject
'2000':{
'January':
[{
name: 'Jerry'
},
{
name: 'Chris'
}]
},
'2001':{
'February':
[{
name: 'John'
}]
}
When I go to update the redux store it is replacing the entire year (eg. '2000') object with the new one that I send to my reducer.
How can I push the the nested array of objects without replacing the entire year object?
My reducer currently looks like:
return Object.assign({}, state, {
...state,
birthdays: Object.assign({}, state.birthdays, {
...state.birthdays,
...birthdays
})
});
Where ...birthdays would be another object in the same format as the first code snippet.
I am also open to suggestions about the structure of my data, normalizing, etc.
Any help would be appreciated.
The object keys in the birthdaysObject are unknown and are assigned when iterating through a separate object. I've tried kolodny/immutability-helper however the $merge function is returning the same results as what my reducer is already doing.
I had the same problem some time ago.
Follow the way I done it.
You have an object, but I think you should have an array of objects.
I also have different names on variables, but this should not be a problem to understand the logic
//do a copy of the array first
let newSubscriptions = state.customer.master.subscriptions.slice();
//for the value you want to change, find it's position in the array first
const indexInSubscriptions = newSubscriptions.map(function(item) {
return item.id;
}).indexOf( action.id);
//get the child you want to edit and keep it in a new variable
const under_edit_subscription = state.customer.master.subscriptions[indexInSubscriptions];
//go again over the array and where is the value at the index find above, replace the value
newSubscriptions = newSubscriptions.map((item, i) =>
i === indexInSubscriptions ? under_edit_subscription : item
)
//add the whole array into the state
return {
...state,
customer: {
...state.customer,
master: {
...state.customer.master,
subscriptions : newSubscriptions
}
}
}

Having issues with updating state of deeply nested arrays in Redux

I've got a state in redux that looks like this:
I'm trying to update the flags of "false" of selected arrays, but have trouble grasping such deeply nested array modification in redux.
Here was my take, that gives me syntax errors:
case 'SET_FLAG':{
return {
...state,
pickedSquares: [
...state.pickedSquares,
pickedSquares[action.index]: [
...state.pickedSquares[action.index],
]
]
}
}
And I probably still need to go 2 levels deeper.
I would appreciate the help.
I recommend making a copy of pickedSquares and then modifying the copy directly. Since pickedSquares is an array, and not a JS object, you won't be able to spread and overwrite values in the manner you are currently trying.
Here is an example of a way to solve the problem based on the code you have provided:
case 'SET_FLAG':
const pickedSquaresCopy = JSON.parse(JSON.stringify(state.pickedSquares)); //make a deep copy
pickedSquaresCopy[action.index] = action.newValue;
return {
...state,
pickedSquares: pickedSquaresCopy
}
What is shown is only a modification one level deep, but you can make them arbitrarily deep by navigating the array/object levels and modifying values that need to be modified.
Maybe not the best way but works:
case "SET_FLAG": {
const newArr = state[action.payload].map(el => {
if (el === false) {
return true;
}
return el;
});
const newState = state.map((el, index) => {
if (index === action.payload) {
return newArr;
}
return el;
});
return newState;
}
I don't know how performant this will be. Maybe you can try to make a deep copy as #Henry Wood suggested and find a way doing nested changes. But with my method, you are not mutating the state directly without doing a deep copy, too.
If you like the ternary operator:
case "SET_FLAG": {
const newArr = state[action.payload].map(el =>
el === false ? true : el
);
const newState = state.map((el, index) =>
index === action.payload ? newArr : el
);
return newState;
}

Redux - returning an array instead of an object in the reducer

I'm trying to make a change on a page, once addListItem is ran an array called "list" that is actually a redux state, needs to be updated. I managed to update it, but instead of an array I return an object. I need to return an array instead, but I don't know how to refactor the code to make it do that.
/**
* Add Item
*/
case 'playlist/addListItem_success': {
return {
...state,
list: {
...state.list,
[action.meta.position]: {
...state.list[action.meta.position],
status: true
}
}
}
}
To return an array, you'd have to use the array-spread syntax (e.g. [...someArray]) instead of object spread, but you can't use that to update a particular index. With a map you can elegantly express what you need though:
return {
...state,
list: state.list.map((e, i) => i === action.meta.position ? {...e, status: true} : e)
};

React-Redux update state of immutable object with nested array, delete element from array

I have the following object, from which I want to remove one comment.
msgComments = {
comments: [
{ comment:"2",
id:"0b363677-a291-4e5c-8269-b7d760394939",
postId:"e93863eb-aa62-452d-bf38-5514d72aff39" },
{ comment:"1",
id:"e88f009e-713d-4748-b8e8-69d79698f072",
postId:"e93863eb-aa62-452d-bf38-5514d72aff39" }
],
email:"test#email.com",
id:"e93863eb-aa62-452d-bf38-5514d72aff39",
post:"test",
title:"test"
}
The action creator hits the api delete function with the commentId:
// DELETE COMMENT FROM POST
export function deleteComment(commentId) {
return function(dispatch) {
axios.post(`${API_URL}/datacommentdelete`, {
commentId
},{
headers: { authorization: localStorage.getItem('token') }
})
.then(result => {
dispatch({
type: DELETE_COMMENT,
payload: commentId
});
})
}
}
My api deletes the comment and I send the comment id to my Reducer, this is working fine to this point, api works and comment is deleted. The problem is updating the state in the reducer. After much trial and error at the moment I am trying this.
case DELETE_COMMENT:
console.log('State In', state.msgComments);
const msgCommentsOne = state.msgComments;
const msgCommentsTwo = state.msgComments;
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload );
const newComments = [
...msgCommentsTwo.data.comments.slice(0, deleteIndexComment),
...msgCommentsTwo.data.comments.slice(deleteIndexComment + 1)
];
msgCommentsOne.data.comments = newComments;
console.log('State Out', msgCommentsOne);
return {...state, msgComments: msgCommentsOne};
Both state in AND state out return the same object, which has the appropriate comment deleted which I find puzzling.
Also the component is not updating (when I refresh the comment is gone as a new api call is made to return the updated post.
Everything else seems to work fine, the problem seems to be in the reducer.
I have read the other posts on immutability that were relevant and I am still unable to work out a solution. I have also researched and found the immutability.js library but before I learn how to use that I wanted to find a solution (perhaps the hard way, but I want to understand how this works!).
First working solution
case DELETE_COMMENT:
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload);
return {
...state, msgComments: {
data: {
email: state.msgComments.data.email,
post: state.msgComments.data.post,
title: state.msgComments.data.title,
id: state.msgComments.data.id,
comments: [
...state.msgComments.data.comments.slice(0, deleteIndexComment),
...state.msgComments.data.comments.slice(deleteIndexComment + 1)
]
}
}
};
Edit:
Second working solution
I have found a second far more terse solution, comments welcome:
case DELETE_COMMENT:
const deleteIndexComment = state.msgComments.data.comments
.findIndex(elem => elem.id === action.payload);
return {
...state, msgComments: {
data: {
...state.msgComments.data,
comments: [
...state.msgComments.data.comments.slice(0, deleteIndexComment),
...state.msgComments.data.comments.slice(deleteIndexComment + 1)
]
}
}
};
That code appears to be directly mutating the state object. You've created a new array that has the deleted item filtered out, but you're then directly assigning the new array to msgCommentsOne.data.comments. The data field is the same one that was already in the state, so you've directly modified it. To correctly update data immutably, you need to create a new comments array, a new data object containing the comments, a new msgComments object containing the data, and a new state object containing msgComments. All the way up the chain :)
The Redux FAQ does give a bit more information on this topic, at http://redux.js.org/docs/FAQ.html#react-not-rerendering.
I have a number of links to articles talking about managing plain Javascript data immutably, over at https://github.com/markerikson/react-redux-links/blob/master/immutable-data.md . Also, there's a variety of utility libraries that can help abstract the process of doing these nested updates immutably, which I have listed at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md.

Resources