React redux not overiding similar object in an array - reactjs

React redux not overiding similar object in an array.
I was expecting arrray of object with different properties. Also If there was any similar property in array than the count will increase from 1 to onwards

Array.push doesn't create array and you should avoid using it.
And you're not returning new state for Case CARTITEMS
you should always return a new state for each case clause.
Also try to split CARTITEMS case clause into 2 different actions, one to increase the quantity and one for adding new item but remember to avoid Array.push() and instead
return {...state, newPropToOverride }

The solution to the question is
case ADDCARTITEMS:
// adding items to cart and removing duplicates while counting them
const isItem = state.CartItems.find(ci => ci.id === action.payload.id)
if(isItem){
return {
...state,
CartItems: state.CartItems.map(item => {
if(item.id === action.payload.id){
return {...item, count: item.count + 1}
} else {
return item
}
})
}
}
else {
return {
...state,
CartItems: [...state.CartItems, {...action.payload, count: 1}]
}
}

Related

Update value in array in reducer

I have a reducer
const initialState = {
elements: [{"flag": false}, {"flag": false}]
};
const checkReducer = function (state = initialState, action) {
switch (action.type) {
case CHANGE_CHECK:
return {...state, //here I want update element by index, that pass in action};
I need to update an existing value in array elements, that I get by the index passed in action.
I can do it like this
state.elements[action.key] = {"flag": !action.flag}
but then I'll change existing state. according to the redux principles, I can't do it.
So I have to use spread operator and change new object. But I don't know how to use it this way.
Tried something like this
...state, elements[action.index]: {"flag": !action.flag}]
but it isn't worked. Is there a way to do what I want?
return {
...state,
elements: state.elements.map((element, index) => {
if (index === action.key) {
return {"flag": !action.flag}
}
return element
})
}
array#map will create a new array, and change only the item whose index match action.key.
If you find this process tedious, you could use libraries that let mutate your state while keeping the reducer returning new state. One of those is immer.

Here is my edit reducer .It works fine but i want to use object.assign method

Here is my reducer in which when todo id matches with the id of payload in action it changes the value but i want to use object.assign metod .
case EDIT_TODO:
return state.map(todo => {
if(todo.id === action.payload.id){
return {
...todo,
text: action.payload.text
}
}
return todo;
});
In the above code you aren't mutating the state since Map returns a new array and also for the id that matches, you are closing the object and updating it.
Only thing is that the other todo object still holds the same reference but that is fine.
And even if text is an object you currently are assigning a new reference to it and if you want to merge you can use the spread syntax but from what it seems by your naming convention text seems to be a string which is javascript is Immutable
case EDIT_TODO:
return state.map(todo => {
if(todo.id === action.payload.id){
return {
...todo,
text: action.payload.text
}
}
return todo;
});
First, as the other response noted, you are not mutating anything (you are returning a new object) with spread syntax.
If you want to achieve the same thing with Object.assign, you can do:
case EDIT_TODO:
return state.map(todo => {
if(todo.id === action.payload.id){
return Object.assign({}, todo, text)
}
return todo;
});
This line:
return Object.assign({}, todo, text)
Says "create a new empty object (first parameter) and assign the following properties to that empty object: todo, text (rest of parameters)"

How to update single value inside array in redux

I am creating a todolist with react and redux and when I update redux state array it doesn't re-render, My state is actually an array which contains objects, something like this:
[{index:1,value:'item1',done:false}, {index:2,value:'item2',done:false}]
What i want to do is on click i want to toggle the value of done to 'true',
But somehow I am unable to do that.
This is what I was doing in my reducer:
list.map((item)=>{
if(item.index===index){
item.done=!item.done;
return [...state,list]
}
But it doesn't re-render even though done keeps changing on clicking the toggle button.
It seems that somehow I am mutating the state.
please tell me where am I going wrong and what should I do instead.
Could you give examples of something similar. I can update state of simple arrays correctly, but doing it for an array containing objects , is what's confusing me.
so, could you give examples of that?
Here's the full reducer code:
export default function todoApp(state=[],action){
switch(action.type){
case 'ADD_TODO':
return [...state,action.item];
case 'TOGGLE_TODOS':
const index = action.index;
const list = state;
list.map((item)=>{
if(item.index===index){
item.done=!item.done;
}
return [...state,list];
});
default:
return state;
}
}
It seems that somehow I am mutating the state.
Correct you are mutating the state, because in js, variable always get reference of object/array. In your case item will have the reference of each object of the array and you are directly mutating the value of item.done.
Another issue is you are not returning the final object properly, also you need to return value for each map iteration otherwise by default it will return undefined.
Write it like this:
case "TOGGLE_TODOS":
return list.map((item) => (
item.index===index? {...item, done: !item.done}: item
))
Or:
case 'TOGGLE_TODOS':
const index = action.index;
const newState = [ ...state ];
newState[index] = { ...state[index], done: !newState[index].done };
return newState;
Full Code:
export default function todoApp(state=[], action){
switch(action.type){
case 'ADD_TODO':
return [...state, action.item];
case 'TOGGLE_TODOS':
const index = action.index;
return state.map((item) => (
item.index===index? {...item, done: !item.done}: item
))
default:
return state;
}
}
Check this snippet:
let list = [
{done:true, index:0},
{done:false, index:1},
{done: true, index:2}
]
let index = 1;
let newList = list.map(item => (
item.index===index? {...item, done: !item.done}: item
))
console.log('newList = ', newList);
Check out the documentation for Array.prototype.Map.
The callback function should return element of the new Array. Try this:
return list.map(item => {
if (item.index === index) {
return {
done: !item.done
...item,
}
return item;
});
Although there already exist two correct answers, I'd like to throw lodash/fp in here as well, which is a bit more dense and readable and also doesn't mutate
import { set } from 'lodash/fp'
return list.map(item => {
if (item.index === index) {
return set('done', !item.done, item)
}
return item
}

Object order changes after reducer updates the state

Currently I'm learning Redux, playing around with different stuff.
So I made a simple TODO app with possibility to edit each TODO item.
But for some reason UPDATE_TODO reducer puts the updated TODO item at the and of the list. So the item is successfully updated but it jumps to the end of TODO's list, instead of staying at the original position.
Original TODO items positioning:
item 1 <-- being updated
item 2
item 3
Positioning after updating of item 1:
item 2
item 3
item 1 <-- was updated
In my reducer I filter all TODOs except updated one with filter() and then set a new state for the updated TODO item.
Need an advice how to update TODO item state properly so it remains at the original position?
reducers
import { UPDATE_TODO } from '../constants';
const initialState = {
all: [] // array of all TODO's
}
export default function(state = initialState, action) {
switch (action.type) {
...
case UPDATE_TODO:
return {
...state,
all: [
...state.all.filter(todo => todo.id !== action.payload.id),
action.payload
]
};
default:
return state;
}
}
Because you are putting the updated item action.payload in the last:
all: [
...state.all.filter(todo => todo.id !== action.payload.id),
action.payload
]
Here filter will return all the todo items other than the updated one, then after all the items you are putting the action.payload (the updated one).
Instead of that use map and put the condition, when todo.id == action.payload.id will be true then return the updated one otherwise return the existing items.
Like this:
case UPDATE_TODO:
return {
...state,
all: [
...state.all.map(todo => {
if(todo.id == action.payload.id)
return action.payload;
return todo;
})
]
};

reducer: adding to array data

if i pull some data from an external source fro the initial state, then want to add additional information like for example 'liked'?
i've tried adding to the products array but its go messy, I'm thinking i should have an additional array for liked items then put the product id in this, the only thing is i need it to reflect in the product that it has been liked and I'm mapping the product data to the item.
whats the best way to go about this ?
const initialState = {
isFetching: false,
products: [],
};
should i add favs: [] ?
how would i reflect the liked state to my product as I'm mapping the products array to the product component? and the liked state is now in the favs?
i tried doing this to add it to the product array but it got really messy (something like this)
case ADD_LIKED:
state.products[action.index]['liked'] = true;
return state;
state.products[action.index]['liked'] = true;
The problem here is that you are mutating the state inside the reducer which is one of the things you should never do inside a reducer.
You'll find that writing functions which don't mutate the data are much easier if you break them down into smaller parts. For instance you can start to split your application up.
function productsReducer(products = [], action) {
// this reducer only deals with the products part of the state.
switch(action) {
case ADD_LIKED:
// deal with the action
default:
return products;
}
}
function app(state = {}, action) {
return {
isFetching: state.isFetching,
products: productsReducer(state.products, action)
}
}
In this case I would definitely want to write a little immutability helper.
function replaceAtIndex(list, index, replacer) {
const replacement = replacer(list[index]);
const itemsBefore = list.slice(0, index),
itemsAfter = list.slice(index + 1);
return [...itemsBefore, replacement, ...itemsAfter];
}
You can complement this with a generic function for changing objects in lists.
function updateInList(list, index, props) {
return replaceAtIndex(list, index, item => {
return { ...props, ...item };
});
}
Then you can rewrite your function in the immutable form
switch(action) {
case ADD_LIKED:
return updateInList(products, action.index, { liked: true });
default:
return products;
}
You could even get fancy by partially applying the function. This allows you to write very expressive code inside your reducers.
const updateProduct = updateInList.bind(this, products, action.index);
switch(action) {
case ADD_LIKED:
return updateProduct({ liked: true });
case REMOVE_LIKED:
return updateProduct({ liked: false });
default:
return products;
}

Resources