Redux, "delete Object" is not working properly in nested Object. I am just trying to delete key "b" in key "0". I am able to just delete the key "0", but when I am trying to delete the nested key "b" in "0", is not working. If you have any idea how to solve it, please, let me know!
Code:
export function removeOrder(key1, key2) {
return {
type: 'REMOVE_ORDER',
payload: {key1: key1, key2: key2},
};
}
var initialState = {
0: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
1: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
};
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
const id2 = action.payload.id2;
// Working
//delete state[id1];
// Not working
delete state[id1][id2];
return state;
default:
return state;
}
}
removeOrder(0, 'a');
The expected result:
{
0: {
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
1: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
};
You can't delete a field from state like that, as that would mutate the actual state object which is one of the basic no-no's in Redux. From their docs:
https://redux.js.org/usage/troubleshooting#never-mutate-reducer-arguments
It is tempting to modify the state or action passed to you by Redux. Don't do this!
For general patterns see immutable-update-patterns. Specifically for your example you need to create a new state object, spreading the values you do want... An easy way to do this is to use lodash's omitBy to exclude the item with the particular key that you don't want in there anymore.
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
return omitBy(state, (value, key) => key === id1);
default:
return state;
}
}
As others have mentioned in the comments, you can ignore id2 if it's just a sub-key of id1.
If you just want to remove a subkey of id1 then you could use this instead, which is a slight modification on the same approach:
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
const id2 = action.payload.id2;
return {
...state,
[id1]: omitBy(state, (value, key) => key === id2)
};
default:
return state;
}
}
You should not mutate state, you can use spread you don't need to add extra dependencies to your project, omit would be a couple of lines of code:
function omit(state, keys) {
const { [keys[0]]: remove, ...rest } = state;
if (keys.length === 1) {
return rest;
}
return {
...state,
[keys[0]]: omit(remove, keys.slice(1)),
};
}
//test code:
const state = {
0: {
b: {
title: 'bb',
},
c: 'not changed',
},
1: 'not changed',
};
console.log('remove 0', omit(state, ['0']));
console.log('remove 0,b', omit(state, ['0', 'b']));
console.log(
'remove 0,b,title',
omit(state, ['0', 'b', 'title'])
);
As Bravo points out, once you delete the "root" property then all nested properties are also removed.
Other than property deletion you've a state mutation issue since you delete a property and then return the same state object.
You need to return a new copy of state, then you can use a single delete referencing the "full" dynamic path to the second nested key.
case 'REMOVE_ORDER':
const { key1, key2 } = action.payload;
// shallow copy state
const nextState = { ...state };
if (state[key1]) {
// shallow copy the nested state
nextState[key1] = { ...state[key1] }
}
// delete the nested property if it exists
delete nextState[key1]?.[key2];
return nextState;
Related
I've got an array of objects and when a certain functions called I want to update a specific array within that array of objects with new data.
Heres the call:
const DataReducer = (state, action) => {
switch (action.type) {
case 'ADD_DATA':
return [...state, state[0].data:[...state, {id: Math.floor(Math.random() * 999),
name: action.payload.name,
}]];
}
}
The problem is I keep getting an error "',' Expected", this appears on the : after data
I'm pretty sure this is correct though, I'm using the context api to update some existing state with new names when the addName function is called.
This should spread the existing state and take the specific state from item[0].data, adding the new name to it but I cant seem to get it working.
Any help would be appreciated.
Here's the original data object: [{title: 'Names', data: []}, {title: 'Meal', data: []}]
It might be easier to break up that return. Make a copy of the state, create an object, and add that to the data array, preserving any objects that are already in there. Then return the copy to update the state.
const DataReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'ADD_DATA': {
const copy = [...state];
copy[0] = {
...copy[0],
data: [
...copy[0].data, {
id: Math.floor(Math.random() * 999),
name: 'Bob'
}
]
};
return copy;
}
}
}
const state = [{title: 'Names', data: []}, {title: 'Meal', data: []}];
const newState = DataReducer(state, { type: 'ADD_DATA', payload: { name: 'Bob' } });
console.log(newState);
return [
{
...(state[0] || {}),
data: {
id: Math.floor(Math.random() * 999),
name: payload.name
}
},
...state?.slice?.(1)
]
I am trying to move an object from one array to another. Think of it like adding / moving a friend from non-friend to friend. I have two arrays, which can be seen below, and I am trying to move an object (i.e. a friend) from possible to current via it's 'id'. In the below example, I am trying to move Parker from possible to current with id = 2.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = Object.assign(
{},
state.possible.splice(action.payload.index, 1)
);
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
Since you can remove multiple elements with splice(), it returns an array. Index the result to get the specific object. You don't need to use Object.assign(), that just copies the value (which just converts the array into an object whose properties are the array indexes).
var state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
};
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = state.possible.splice(action.payload.index, 1)[0];
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
state = addFriend(state, {payload: { index: 0 }});
console.log(state);
I'm not sure why you're returning a new state object, since you're modifying the old state in place.
It is not that time-efficient if you want a fast running code. But it follows immutability.
We just ignore the item from possible, and add it to current.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
return {
...state,
current: current.concat(possible[action.payload.index]),
possible: possible.filter((_, index) => index !== action.payload.index)
}
}
state = addFriend(state, {payload: {index: 0}})
console.log(state)
I have a cart data in this form
const cart = {
'1': {
id: '1',
image: '/rice.jpg',
price: 32,
product: 'Yellow Corn',
quantity: 2,
},
'2': {
id: '2',
image: '/rice.jpg',
price: 400,
product: 'Beans',
quantity: 5,
},
'3': {
id: '3',
image: '/rice.jpg',
price: 32,
product: 'Banana',
quantity: 1,
},
};
In the reducer file I have a function removeItem that is being consumed by the reducer
const removeItem = (items, id) => {
items[id] && delete items[id];
return items;
};
case REMOVE_ITEM: {
const { cart } = state;
const {
payload: { id },
} = action;
return {
...state,
cart: removeItem(cart, id),
};
}
In the component I am using this handleRemove() to handle the deletion
handleRemove = id => {
const {
actions: { removeItem },
} = this.props;
const payload = { id };
removeItem(payload);
};
Now in the redux developer tool, the change is working effectively but the component view is not updating.
Change removeItem function to below code
const removeItem = (items, id) => {
items[id] && delete items[id];
return {...items};
};
This is because component gets change only if reference changes. You can refer this link for more explanation
You need to create a copy of the cart, as otherwise React won't detect the change, because it does reference comparison and you return the same object.
Try to do the removeItem() in this way.
const removeItem = (items, id) => {
let itemsClone = [...items]; // Copies all items into a brand new array
itemsClone [id] && delete itemsClone [id]; // You perform the delete on the clone
return itemsClone ; // you return the clone
};
Do not mutate redux state, redux does not perform a deep diff check in your objects, when you do not mutate and create new objects, it is automatically detected as a different object, because its plain old js objects.
this would be good for further reading : immutable-update-patterns
so your removeItem method should be,
const removeItem = (items, id) => {
let {[id]: remove, ...rest} = items
return rest;
}
You can also use a library to do this, such as dot-prop-immutable , which has set, remove, merge methods to do relevant operations without mutating the object.
const state = [
{
list: []
}
];
The list is a list of student objects, for example:
list: [
{ id: 1, name: "Mark", attendance: true },
{ id: 2, name: "John", attendance: false }
]
I have a button that triggers a post request to an API to change attendance to true. Post request returns the student object that was changed as in e.g.:
{ id: 2, name: "John", attendance: true }
This works fine and if no errors, will dispatch ATTENDANCE_SUCCESS.
Now, with this kind of set-up:
export function students(state, action) {
let latestState = state[state.length - 1],
newState = Object.assign({}, latestState);
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
// Need to change redux state 'attendance' value to true for a student with ID returned from the POST request
}
}
Initially, I did:
const studentChanged = newState.list.find(function(student) {
return (
student.id ===
action.res.data.id
);
});
studentChanged.attendance = true;
But it mutates the state in the redux store (although I am not sure how it's exactly happening since I assumed newState is already a copy).
What's the proper way?
The following would update a single item in the array. The critical aspect here is that if the id of the item does not match the id from the action payload, it returns the item unaltered, otherwise it updates the attendance property. Array.prototype.map returns a new array so it would be immutable.
export function students(state, action) {
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
return state.map(student => {
// we want to leave non matching items unaltered
if (student.id !== action.res.data.id) {
return student;
}
return { ...student, attendance: true };
});
}
return state;
default:
return state;
}
}
Here is a StackBlitz to demonstrate the functionality.
Hopefully that helps!
This is one of my redux reducers and I feel it looks very ugly. Is it possible to improve it?
The goal that I want to achieve is very simple:
If I already have this item in my current state, increase the quantity by 1,
otherwise add this item to state.
function globalReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART: {
let { item } = action;
if (state.getIn(['sideCart', 'orderItems', item.id])) {
item.quantity = state.getIn(['sideCart', 'orderItems', item.id]).get('quantity') + 1;
} else {
item.quantity = 1;
}
item = fromJS(item);
const newState = state.setIn(['sideCart', 'orderItems', item.get('id')], item);
return newState;
}
default:
return state;
}
}
The state should look like this:
sideCart: {
orderItems: {
1: {
id: 'orderItems-1',
name: 'AI Brown\'s BBQ Fillet of Beef with Brown Mushroom Brandy Sauce',
quantity: 10,
price: 12,
subitems: ['0', '1', '2'],
instruction: 'No rosemary for beef',
},
2: {
id: 'orderItems-2',
name: 'AI Brown\'s BBQ Fillet',
quantity: 10,
price: 14,
subitems: ['0', '1', '2'],
instruction: 'No rosemary for beef',
},
},
}
This is how I would enhance it syntactically:
const reduceCart = (state, action) => {
let { item } = action;
const stateIn = state.getIn(['sideCart', 'orderItems', item.id]);
item.quantity = stateIn
? stateIn + 1
: 1;
item = fromJS(item);
return state.setIn(['sideCart', 'orderItems', item.get('id')], item);
};
const globalReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART: return reduceCart(state, action);
default: return state;
}
};
I found the same complexity when using immutable.js to handle deeply nested objects. I have made a lightweight immutable helper: ImmutableAssign that allows you to continue working with plain JS objects, which will simplify your operations.
In the following example, it expects state and action to be plain JS objects, and it will return you a new state as plain JS object:
function globalReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_CART: return addCart(state, action);
default: return state;
}
}
function addCart(state, action) {
let { item } = action;
return iassign(state,
function(state, context) {
return state.sideCart.orderItems[context.item.id]
},
function(selectedItem) {
item.quantity = selectedItem.quantity ? selectedItem.quantity + 1 : 1;
return item;
},
{ item: item });
}
// The first parameter is a function that return the
// property you need to modify in your state.
// This function must be **pure function**, therefore "item"
// need to be passed in via the context parameter.
//
// The second parameter is a function that modify selected
// part of your state, it doesn't need to be pure, therefore
// you can access "item" directly
//
// The third parameter is the context used in the first
// function (pure function)