Filtering Arrays in a Reducer - Redux - javascript

Following a React tutorial, I see this code in a reducer to remove a message from an array using its ID:
Wouldn't this be better written as:
else if (action.type === 'DELETE_MESSAGE') {
return {
messages: [
...state.messages.filter(m => m.id === action.id)
],
};
};
I thought for a second that filter might modify state and return the same array but according to MDN it creates a new array.
Am I safe, and is my implementation correct?

Yes. It would actually be a very clean solution. The trick is that, in Array#filter, every element of an array is applied with a function that accepts more than one argument. Such a function, when returns a boolean value, is called predicate. In case of Array#filter (and in fact, some other methods of Array.prototype), the second argument is index of the element in source array.
So, given that you know the index, it's simply
(state, action) => ({
...state,
messages: state.messages.filter((item, index) => index !== action.index)
})
However, you don't know the index. Instead, you have a value of id property. In this case, you're right, you simply need to filter the source array against this id to only put elements that have value of id that is not equal to target id:
(state, action) => ({
...state,
messages: state.messages.filter(item => item.id !== action.id)
})
Take a note: no need to spread state.messages and put them back into a new array. Array#filter doesn't mutate the source array, which is nice.
So, it's !== instead of ===. You were close.

Related

Update array in array of object using previous state in react

I have the following state in my app:
const [education, setEducation] = useState([
{
institution: "University",
area: "President",
studyType: "Bachelor",
startDate: "2013-01-01",
endDate: "2014-01-01",
gpa: "4.0",
courses: ["DB1101 - Basic SQL"],
},
]);
And the following method to update the state:
const onCoursesChange = (newValue, index) => {
const newValuesArr = newValue ? newValue.map(item => item.value) : [];
setEducation((prev)=>(
prev[index].courses=newValuesArr
));
//setEducation(education[index].courses = newValuesArr);
console.log(education[index].courses)
};
With the above code I get the error Cannot create property 'courses' on string 'asdfa' when the array newValuesArr has more than one element.
How do I update the state ??
The problem is here:
setEducation((prev)=>(
prev[index].courses=newValuesArr
));
prev[index].courses=newValuesArr is an assignment that simply returns the value being assigned, here newValuesArr. This is an array of strings, so its elements do not have the properties you expect.
Your state-updating function (the one passed to setEducation) instead needs a return the whole of prev, with the update you desire. And this should be done immutably - that is, without updating prev. Luckily, ES6 offers nice tools for doing this. Here is how you can do it in this case:
setEducation((prev)=>(
prev.map((item, oldIndex) => oldIndex === index ? { ...item, courses: newValuesarr } : item )
));
That is, it maps through the array prev, and leaves all unchanged apart from the one with the matching index, and in that one updates the courses property to the desired array.

Redux store is not updating when modifying element propriety of an array object

I have below code
case 'COMPLETE_TODO' :
state.todos[state.todos.findIndex((obj => obj.id == action.value))].status = "COMPLETED"
return {
...state,
todos: state.todos
}
I feel like an array is not taken as a modified array because just a property of one single element has been updated.
Any idea?
Thanks
Do not mutate state, make a copy of state and then perform operations on it
case 'COMPLETE_TODO' :
return {
...state,
todos: state.todos.map(obj=> ({...obj, status: obj.id == action.value ? "COMPLETED" : obj.status}))
}
map create a new array, ... spread syntax creates a shallow copy, if you object is deeper then one level, then you should do a deep clone,
For deep cloning you can use
let deepCopy = JSON.parse(JSON.stringify(state.todos))

Mapping an object, converting into an array, then convert back to object

I have this code that i use in a redux reducer:
case 'REMOVE_FL_EVENT' :
return{
...state,
events: Object.keys(state.events).map(group => {
return state.events[group].filter(item => item.id !== action.id)
})
}
What happens here is that the state.events is an object, where every key is the name of the group of it's events, the value is an array with the events. What i want to do is when i convert the object into an array with map, if the filter happened convert back to it's original state, where state.events is not an array, but an object, with the original names of the keys.
There is no need to use map, you could use reduce only, something like:
{
...state,
events: Object.keys(state.events).reduce(
(obj, event) => ({
...obj,
[group]: state.events[group].filter(item => item.id !== action.id)
}),
{}
)
};
Update
The reduce has the following signature:
arr.reduce(callback[, initialValue])
So in our script, we are giving an empty object as the initial value for the accumulation.
You can use map/reduce for this purpose. First map it and then reduce it into an object.
case 'REMOVE_FL_EVENT' :
return{
...state,
events: Object.keys(state.events).map(group => {
return { [group]: state.events[group].filter(item => item.id !== action.id) }
}).reduce((obj, event) => Object.assign(obj, event), {})
}
The output will be an object with keys as groups. Let me know if it works.
With standard JS, you can use reduce to convert the array back to an obj:
arr.reduce((acc, o) => Object.assign(acc, o), {})
Using ramda.js you can filter Objects and their nested properties. https://ramdajs.com/docs/#filter

Typescript : filter a table and return each mapped item

i have a an array mapping action looking like this :
this.itemsList= res.map( ( x, index ) => {
x.id = x.code;
x.itemName = x.name;
return x;
} );
I ve tried to optimize it like this (2nd manner):
this.itemsList = res.map(({code: id, name: itemName}) => ({id, itemName}));
but i need to return each mapped element of the array (return x)
i wonder how to do it using my optimized manner (2nd)
ideas ??
You can use the ... spread operator to spread the remaining object properties into a value, and the use the spread operator again to spread those properties from the stored object back into the target object.
res.map(({ code: id, name: itemName, ...otherProps }) => ({
id, itemName, ...otherProps,
}));
Note that this does remove the original code and name properties. If you still need those, you'll have to add them explicitly as well.
res.map(props => ({
id: props.code,
itemName: props.name,
...props,
}));
When you say filter I believe you are referring to the plucking of specific properties -- in this case using destructuring. This is different than the collection filter operation which removes elements from a collection based on the result of a projected function.

How to delete object from array using object property - React

I have a todo list that holds a delete button in a grandchild, that when clicked fires an event in the parent - I am wanting this event to delete the array entry corresponding to the grandchild clicked.
Parent (contains the array and my attempt at the function)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
// taskToDelete is the name of the task - doesn't contain an object
deleteTask(taskToDelete) {
this.state.tasks.remove(task => task.name === taskToDelete);
this.setState({ tasks: this.state.tasks });
}
Any help would be appreciated
Two issues there:
You're seeming to try to direct modify this.state.tasks. It's important not to do that, never directly modify this.state or any object on it. See "Do Not Modify State Directly" in the React documentation for state.
You're passing an object to setState that is derived from the current state. It's important never to do that, too. :-) Instead, pass setState a function and use the state object it passes you when calling that function. From "State Updates May Be Asynchronous" in the documentation:
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state... [Instead]...use a second form of setState() that accepts a function rather than an object.
(my emphasis)
I figure your remove on an array was intended to be hypothetical, but for the avoidance of doubt, arrays don't have a remove method. In this case, the best thing to do, since we need a new array, is to use filter to remove all entries that shouldn't still be there.
So:
deleteTask(taskToDelete) {
this.setState(prevState => {
const tasks = prevState.tasks.filter(task => task.name !== taskToDelete);
return { tasks };
});
}
You could simply filter the array :
this.setState(prevState => ({
tasks: prevState.tasks.filter(task => task.name !== 'taskToDelete')
}));
Also when updating based on this.state, its better to use the function form because setState is async.
You can use filter to remove one object from an array following the immutable pattern (filter will create a new array) :
deleteTask(taskToDelete) {
const newTaskArray = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: newTaskArray });
}
Edit : codepend of the solution : https://codepen.io/Dyo/pen/ZvPoYP
You can implement deleteTask method as below:
deleteTask(taskToDelete) {
this.setState((prevState, props) => {
const tasks = [...prevState.tasks];
const indexOfTaskToDelete = tasks.findIndex(
task => task.name === taskToDelete
);
tasks.splice(indexOfTaskToDelete, 1);
return { tasks };
});
}
A. Find the index of taskToDelete.
B. Then use splice method to delete the item from the collection
C. Then call setState to update the state with tasks.
You can use higher order function Array#filter to delete the task.
let updatedTasks = this.state.tasks.filter(task => task.name !== taskToDelete);
this.setState({ tasks: updatedTasks });
I have followed below steps to delete a particular selected Object from the state array:
Here I am using a list of checkBoxes, when I am selecting a checkBox it will add it in the state array and when it gets de-selected then it will get deleted from the array.
if (checked) {
var tempObject = { checkboxValue: data, label: title }
this.state.checkBoxState.push(resTemp);
} else {
var element = data; //data is coming from different method.
for (let index = 0; index < this.state.checkBoxState.length; index++) {
if (element === this.state.checkBoxState[index].checkboxValue) {
this.state.checkBoxState.splice(index, 1);
}
}
}
I got stuck for this question and I am sharing my solution. Hope it will help you.

Categories