How to spread state into a specific array - javascript

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)
]

Related

Redux, "delete Object" is not working properly in nested Object

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;

How do you prevent a javascript object from being converted into a length 1 array of objects?

I'm working on my first solo ReactJS/Redux project and things were going well until I got to a point where I'm using an object in the Redux store that is always supposed to be a single object. When I copy the object from one part of the store (one element of the sources key) to another (the selectedItems key) that object is being stored as an array of length 1, which isn't the data I'm passing in (it's just a single object). I could live with this and just read out of that store variable as an array and just use element 0 except that when I call another method in the reducer to replace that variable in the store, that method stores the new data as a single object! My preference would be to have everything store a single object but I can't figure out how to do that. Anyway, here's some of the reducer code:
const initialState = {
sources: [
{
id: 1,
mfg: 'GE',
system: 'Smart bed',
model: '01',
name: 'GE smart bed'
},
{
id: 2,
mfg: 'IBM',
system: 'Patient monitor',
model: '03',
name: 'IBM patient monitor'
}
],
error: null,
loading: false,
purchased: false,
selectedItem: {}
};
// This is called when a user selects one of sources in the UI
// the Id of the selected sources object is passed in as action.id
// This method creates an array in state.selectedItem
const alertSourceSelect = ( state, action ) => {
let selectedItem = state.sources.filter(function (item) {
return item.id === action.id;
});
if (!selectedItem) selectedItem = {};
return {...state, selectedItem: selectedItem};
};
// When someone edits the selected source, this takes the field name and new value to
// replace in the selected source object and does so. Those values are stored in
// action.field and action.value . However, when the selected source object is updated
// it is updated as a single object and not as an array.
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: updateObject(state.selectedItem[0], { [action.field] : action.value })
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ALERT_SOURCE_SELECT: return alertSourceSelect( state, action );
case actionTypes.ALERT_SELECTED_SOURCE_EDIT: return selectedSourceEdit( state, action );
default: return state;
}
Here is the updateObject method (sorry I left it out):
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Issue : updateObject is returning object and not array,and you are maintaining selectedItem as an array not object
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Solution :
Either return array from updateObject :
export const updateObject = (oldObject, updatedProperties) => {
return [{
...oldObject,
...updatedProperties
}]
};
OR make array of returned object
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: [updateObject(state.selectedItem[0], { [action.field] : action.value })]
};
};

How do you move an object from one array to another array by feature?

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)

How to add a new item into array in an object that's is in the array?

Here is my state
const initState = [
{
id: 1,
criteria: [],
isInclusion: true,
},
{
id: 2,
criteria: [],
isInclusion: true,
},
];
I am trying to add a new object into criteria array with my dispatch
dispatch(
addCriteria({
id: item.id,
type: item.type,
groupId: 1,
})
);
in the reducer I do the following but it doesnt add to an array of an existing group but instead, it adds as a new object to the array with that criteria added in it.
const addReducer = (state = initState, action) => {
switch (action.type) {
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = [
...state,
state.map(group =>
group.id === action.payload.groupId
? {
...group,
criteria: newCriteria,
}
: group
),
];
return newState;
}
}
};
You are adding all items to the state object (also the one you want to modify) with ...state and than you map over it again. This will not work.
You should change your state object to be an object to access the items by reference and not index and use the id as access. This will also be faster(thanks #Yash Joshi ):
const initState = {
1: {
criteria: [],
isInclusion: true,
},
2: {
criteria: [],
isInclusion: true,
},
};
This will let you access and update the state more easily and easier to stay immutable.
This will let you update it like this:
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = {
...state,
[action.payload.groupId]: {
...state[action.payload.groupId],
criteria: [
...state[action.payload.groupId].criteria,
newCriteria
],
}
};
return newState;
To add a new item to it:
const newState = {
...state,
[action.payload.groupId]: {
isInclusion: false,
criteria: [ ],
}
};
Hope this helps. Happy coding.
Try spread operator (es6):
return [
...state,
newCriteria,
]
This will return a new array with the newCriteria object on it.
Here is how I'd do it:
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = state.map(gr => {
if (gr.id !== action.payload.groupId) {
return gr;
}
return {
...gr,
criteria: [...gr.criteria, newCriteria];
}
});
I think you should follow Domino987 approach it will be faster. But if you still wish to continue with your approach. You can try this:
const newState = state.map(item => item.id === newItem.id ? ({ ...item, ...newItem }) : item );
return newState;
Hope this Helps!

React component does not update with the change in redux 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.

Categories

Resources