Having issues with updating state of deeply nested arrays in Redux - reactjs

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;
}

Related

Why am I mutating the original array with filter method? [React]

After executing the newData[0].id = newValue I am actually updating the react initialData state. How is that possible?
Is my understanding that filter should return a new array different than the original one, also I am not ussing the setState feature so I don't understand why the state is changing.
Because arrays are mutable. it will keep the reference to the original array even after filtering.
use the spread operator to avoid mutating the original array
const data = [...newData]
data[0].id = newValue
As per the new beta docs on updating items in array
setInitialData(prev => {
// create a new array
const withReplaced = prev.map(elem => {
if (elem.id === id) {
const newVal = //set new value
// create a new updated element
return {
...elem,
id: newVal
}
} else {
// The rest haven't changed
return elem;
}
});
return withReplaced;
})
Hope it helps
you can't update the initialData,but the you can update the son of the array.And if you don't use "setData".The views won't change.

A better way to handle updating state in reducer

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: [] }.

How to update object in vuex store array?

I'm pretty new to vue/vuex/vuetify but starting to get the hang of it.
I have a problem though I haven't been able to solve properly.
I have an array of "projects" in my store. When deleting and adding items to the store via mutations the changes reflect properly in subcomponents referencing the array as a property.
However, changes to items in the array does not reflect even though I can see that the array in the store is updated.
The only way I got it to "work" with an update action was to either :
remove the project from the array in the store and then add it
use code that sort of does exactly the same as described above but like so:
state.categories = [
...state.categories.filter(element => element.id !== id),
category
]
But the problem with the above two methods is that the order of the array gets changed and I would really like to avoid that..
So basically, how would I rewrite my mutation method below to make the state reflect to subcomponents and keep the order of the array?
updateProject(state, project) {
var index = state.projects.findIndex(function (item, i) {
return item.id === project.id;
});
state.projects[index] = project;
}
You can use slice to inject edited project in correct position:
updateProject(state, project) {
var index = state.projects.findIndex(function(item, i) {
return item.id === project.id;
});
state.projects = [
...state.projects.slice(0, index),
project,
...state.projects.slice(index + 1)
]
}
or you can use JSON.parse(JSON.stringify()) to make a deep copy of object
updateProject(state, project) {
var index = state.projects.findIndex(function(item, i) {
return item.id === project.id;
});
state.projects[index] = project;
state.projects = JSON.parse(JSON.stringify(state.projects))
}

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: View not getting updated even though mapStateToProps is

My Application and Store
Using Redux with ReactJS, I am keeping an array of objects (called results) in the store and dispatching actions that sort and manipulate it. In mapStateToProps, I return this results array, which renders the results in a list on the view.
// in Component file
export default class ResultList extends Component {
constructor(props) {
super(props);
this.renderResults = this.renderResults.bind(this);
}
renderResults(results) {
return (
results.map((result) => {
return <AnotherComponent />
})
);
}
render() {
const { results } = this.props;
return (
<div>
<ul>
{this.renderResults(results)}
</ul>
</div>
)
// in Container Component file
const mapStateToProps = (state, ownProps) => {
console.log('map state is triggered');
return {
results: state.results
};
}
The Problem
I have found that although my mapStateToProps successfully triggers to update the props to my class component, for some reason the view only updates on the FIRST time that the results array gets manipulated, but does not update on future updates to the store. (Please see update below. This is not entirely accurate.)
I have made sure that this problem is not due to mutating state in my reducers as is often the case; I have confirmed that the mapStateToProps runs every time that the store gets updated (as indicated by the console.log). The problem seems to be between the results props getting returned from mapStateToProps and the view actually rendering the results array, but I do not have visibility to see what Redux is doing under the hood.
The closest problem that someone else has had to what I am experiencing seems to be this, but I do not know how or if this fix applies to my use of a stored array: https://forums.meteor.com/t/the-state-is-correctly-mutated-and-returned-but-view-does-not-rerender/28840/5
Any help would be greatly appreciated. Thank you.
Update
I apologize, but must correct my statement above saying that there are no further updates to the view after the first time the results in the store gets updated. With further testing I have found that is not completely true. The view gets updated only when the results array is sorted according to case 1 of the below sorting function. Cases 2 and 3 are the ones that result in no update to the view. This may/may not be necessary information, but the results array gets sorted by these 3 cases in a cycle onClick in the following order: case 1, case 3, case 2.
// in reducer index.js file
case SORT_RESULTS:
return {
...state,
results: sortArr(state.results, state.sortType)
};
// sorting function
function sortArr(arr, sortType) {
let newArr = [];
switch (sortType) {
case '1':
for (let i = arr.length - 1; i >= 0; i--) {
newArr.push(arr[i]);
}
return newArr;
case '2':
newArr = arr;
newArr.sort((a, b) => {
return b.num - a.num;
});
return newArr;
case '3':
newArr = arr;
newArr.sort((a, b) => {
let id1 = a.id.toLowerCase();
let id2 = b.id.toLowerCase();
if (id1 < id2) {
return -1;
}
if (id1 > id2) {
return 1;
}
return 0;
});
return newArr;
default:
return arr;
}
}
The Solution
Here is the culprit: newArr = arr. Change this to newArr = arr.slice(), and the view will get updated on every sort.
Possible Root Cause
As to why this solution works, I invite anyone else's perspective. Here are my thoughts:
According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice:
"The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included)."
Copying the results array using newArr = arr seems to only copy the pointer (to the array), which is really just a reference in memory. If this statement is true, then Redux would not consider the state to have been changed at all because it is comparing the exact same pointer (state.results), even though the actual data of the array itself is confirmed to change by sorting. Copying a completely new array through slice() would involve a new pointer and data, which would be detected as a change to Redux under the hood.

Resources