Replacing object for another in state React - reactjs

I have an array of objects. I find the object I need to replace but can't replace it in state. The original object stays untouched and the object I want to replace it with is added to the end of the array. I'm use React hooks. Thanks
case types.ADD_USERNAME:
let task = action.payload
let assignedTask = state.tasks.find(el => el.id === task.id)
let index = state.tasks.indexOf(assignedTask)
return {
...state,
tasks: [
...state.tasks,
assignedTask = {
...assignedTask,
user: task.user.name
}
]
}

You can make use of map instead of going by index and find element to update the state which in my opinion is a cleaner approach
case types.ADD_USERNAME:
let task = action.payload;
return {
...state,
tasks: state.tasks.map(el => {
if(el.id == task.id) {
return { ...el, user: task.user.name}
}
return el;
});
}
However if you wish to go by the index and find element approach, you need to slice the task array and update the specific index element
case types.ADD_USERNAME:
let task = action.payload
let index= state.tasks.findIndex(el => el.id === task.id);
return {
...state,
tasks: [
...state.tasks.slice(0, index - 1),
{
...state.tasks[index],
user: task.user.name
}
...state.tasks.slice(index + 1)
]
}

You could achieve this by using map and matching your object by id or any other unique identifier:
case types.ADD_USERNAME:
let task = action.payload;
return {
...state,
tasks: state.tasks.map(t => {
if (t.id === task.id) {
return {
...t,
user: task.user.name
};
}
return t;
})
};

Related

How do I check if an item exists and modify it or return the original object

export const basketReducer = (state = { total:0, items:[]}, action) => {
switch (action.type) {
case "ADD_ITEM":
const item = [...state.items, action.payload]
const updateBasket = state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return item
})
It seems your code is close.
First check if state.items array already contains some element with a matching _id property.
If there is a match then shallow copy the state and shallow copy the items array and update the matching element.
If there is no match then shallow copy the state and append the new data to the items array.
Reducer case logic:
case "ADD_ITEM":
const hasItem = state.items.some(el => el._id === action.payload._id);
if (hasItem) {
// update item
return {
...state:
items: state.items.map(el => {
if (el._id === action.payload._id) {
return {
...el,
quantity: el.quantity + action.payload.quantity
}
}
return el; // <-- return current mapped element if no change
}),
};
} else {
// add item
return {
...state,
items: state.items.concat(action.payload),
};
}

Update the Old and New Value in Redux

I have a problem updating the old value of the array in my redux react app. I have successfully updated the new selected object true. I want to other object to set to false since I have set the another object to true.
const initialState = {
annualPlans: [],
};
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};
It seems like you want to return current set to false for the others:
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return with current false
{ ...todo, current: false }
Id first create the new todos by looping though a map and then assign to the state
case planConstants.UPGRADE_PLAN_SUCCESS: {
const newTodos = state.annualPlans.map((todo) => {
if (todo.value === action.data.plan_id) {
return { ...todo, current: true }; // if todo.id matched then set the current to true and return;
}
if (todo.current) {
return { ...todo, current: false }; // else if current is already true, then false it and return
}
return todo; // else return original todo
});
return {
...state,
annualPlans: newTodos
};
}
.....
This will optimize the rendering and prevent of looping multiple times.
No need to re create all todo items, if you use pure components then that will mess up this optimisation. You can just add a map to deal with resetting the current value:
const planReducer = (state = initialState, action) => {
switch (action.type) {
case planConstants.UPGRADE_PLAN_SUCCESS:
return {
...state,
annualPlans: state.annualPlans
.map(
(todo) =>
todo.current
? { ...todo, current: false } //reset current to false
: todo // no need to change this one
)
.map((todo) =>
todo.value === action.data.plan_id
? // transform the one with a matching id
{ ...todo, current: true }
: // otherwise return original todo
todo
),
};
default:
return state;
}
};

how to delete nested object in React with spread function

I am working on a simple cart function with react-redux, and I have an object that is structured as below:
{
0: { // product ID
"S" : 1, //product variant and item count
"M" : 1
},
1: {
"L":1
"XL": 5
},
}
I wanted to remove the property based on user action but I was not able to achieve that so far.
Attempt 1: delete function will remove everything within the state instead of removing the selected property.
case REMOVE_PRODUCT_FROM_CART:
let newObject = Object.assign({}, state)
return delete newObject[productId][varient];
Attempt 2: only managed to set the property to null but still not able to remove the item.
case REMOVE_PRODUCT_FROM_CART:
return {...state,
[productId]: {
// check if property exists
...(state?.[productId] ?? {}),
[varient]: null
}
Is there any way to remove the desired property with the spread operator?
Here is one way to do it:
case REMOVE_PRODUCT_FROM_CART:
const newObject = {...state[productId]}
delete newObject[varient]
return {...state, [productId]:newObject}
Here is another way to do it:
const state = {
22: {
ok: 88,
remove: 22,
},
};
const productId = 22;
const varient = 'remove';
const { [varient]: ignore, ...product } = state[productId];
console.log('new state', {
...state,
[productId]: product,
});
Here is another option using Object.entries and Object.fromEntries
const state = {
pId: {
k1: 'v1',
k2: 'v2',
k3: 'v3',
},
};
const reducer = (productId, variant) => {
return {
...state,
[productId]: Object.fromEntries(
Object
.entries(state[productId])
.filter(([key, val]) => key !== variant)
),
}
}
console.log(reducer('pId', 'k2'))

Toggle state of object key value in an array

I need to create a reducer that toggles the state of done using the id or index of the todo
state = {
todos: [
{
title: "eat rice",
done: false,
id: 1
},
{
title: "go fishing",
done: true,
id: 2
},
{
title: "drink coffee",
done: false,
id: 3
}
]
}
I tried this but it mutates the state, the payload being the index of the object in the array.
case "DONE":
const todos = [...state.todos];
todos[action.payload].done = !todos[action.payload].done;
return {
...state,
todos: todos
};
You could use a map function instead. The function will generate a new array which you can use to replaces todos with.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
// Based on your code, I assume action.payload is the index of the todo in the array of todos
if (index === action.payload) {
const newTodo = {...todo};
todo.done = !todo.done;
return todo;
}
return todo;
});
return {
...state,
todos: newTodos,
};
If you don't want to iterate over every todo, you could do something else such as using slice to create a copy of the array and then change the one value:
case "DONE":
const newTodos = todos.slice();
const updatedTodo = {...newTodos[action.payload]};
updatedTodo.done = !updatedTodo.done;
newTodos[action.payload] = updatedTodo;
return {
...state,
todos: newTodos,
};
Found the answer. Thanks for the contributions.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
if (index === action.payload) {
const newTodo = { ...todo };
newTodo.done = !newTodo.done;
return newTodo;
}
return todo;
});
return {
...state,
todos: newTodos
};
Using the spread operator or map will create a new array but will not automatically clone the contained objects, as JavaScript follows "pass by reference". You'd have to clone the object as well. So maybe something like
case "DONE":
const todos = state.todos.map((todo, index) => {
const newTodo = {...todo};
if (action.payload === index) {
newTodo.done = !todo.done;
}
return newTodo;
});
return {
...state,
todos,
};
Of course you could also use a clone utility or something like Immutable.js.

React Reducer function to return array of objects

If I have an array of objects representing say a shopping cart (in React)
[ { id: 1, qty: 1 } ]
what would be a neat way of adding an item to this state ?
I would need to check if the array contains an object with that id, if it did then update it's quantity, if not then just create a new array element adding this new object.
I am getting myself in a mess trying to write this with new ES6 syntax in a functional style ... this is what I have so far ...
let initialState = []
export default function shoppingCart(state=initialState, action) {
switch(action.type) {
case 'ADD_TO_CART':
// check the array of objects and only act on the one we are interested in
let newQty = action.payload.qty
console.log("state : ", state)
for(let i = 0; i < state.length; i++) {
if (state[i].id === action.payload.id) {
newQty = state[i].qty + action.payload.qty
console.log("QTY : " + newQty)
}
}
//Obviously this will not work if the {id ...} already exists
return [ ... state, {id:action.payload.id, qty:newQty} ] // todo: update this to contain the payload with qty included
case 'REMOVE_FROM_CART':
return state.filter(elem => elem.id !== action.payload.id)
default:
return state
}
}
You can use Array.prototype.splice to replace array item with another one:
case 'ADD_TO_CART':
const index = state.findIndex(item => item.id === action.payload.id)
if (index > -1) {
state.splice(index, 1, {
id: action.payload.id,
qty: state[index].qty + action.payload.qty
})
return [...state]
}
else {
return [...state, {
id: action.payload.id,
qty: newQty
}]
}

Resources