Remove and Add items from array in nested javascript object [closed]

Posted on

Problem

I have the following object at React state:

groups: [
    {
        id: 1,
        items: [
            {id: 1},
            {id: 2},
            {id: 3}
        ]
    },
    {
        id: 3,
        items: [
            {id: 1},
            {id: 3},
            {id: 4}
        ]
    }
]

I have written the following functions to add and remove items:

  removeItem(groupId, itemId) {
    // make a copy
    let {groups} = this.state

    const gIndex= groups.findIndex(g=> g.id == groupId);
    let items = groups[gIndex].items
    groups[gIndex].items = items.filter((i) => i.id !== itemId)

    this.setState({groups})
  }

  addItem(groupId, itemId) {
    // make a copy
    let {groups} = this.state 

    const gIndex = groups.findIndex(g => g.id == groupId);
    let items = groups[gIndex].items
    groups[gIndex].items = items.concat({id: itemId})

    this.setState({groups})
  }

Is there any way to write it cleaner?

Solution

Not a copy

// make a copy
let {groups} = this.state

You comment that the line following makes a copy. This is not the case, you are just creating a new reference named groups to the array..

Use const

As you do not change the reference you should define groups as a constant.

const {groups} = this.state;
// or
const groups = this.state.groups;

Array.push rather than Array.concat

Array.concat creates a new array, it is more efficient to just Array.push a new item to the existing array

groups[gIndex].items = items.concat({id: itemId});
// can be
groups[gIndex].items.push({id: itemId});

Array.find rather than Array.findIndex

You find the index of the group you want to modify. This complicates the code. If you use Array.find it will return the group and you don’t need to index into groups each time.

const gIndex= groups.findIndex(g=> g.id == groupId);
// becomes 
const group = groups.find(group => group.id == groupId);

Common tasks in functions

Between the two functions you repeat some code. I would imaging that other functions also need to access groups by id, make changes, and set the state of the new content. It would be best to provide functions to do that.

getGroupById(id) { return this.state.groups.find(group => group.id === id) },
updateGroups() { this.setState({groups: this.state.groups}) },

Good naming

The functions addItem and removeItem do not indicate that they are related to adding/removing from a group. Better names could be removeItemFromGroup, addItemToGroup

Be consistent

Good code style is consistent. Whether or not you use semicolons you should avoid doing it sometimes, do it always or never. (best option is use them)

Rewrite

Using the above points you could rewrite the code as the following.

Added two functions to do the common task of getting by group id and updating the state with groups.

I assume that the groupId will exist as the code you have given indicates this to be so, if the groupId does not exist your code would throw, and so would the following code.

getGroupById(id) { return this.state.groups.find(g => g.id === id) },
updateGroups() { this.setState({groups: this.state.groups}) },
removeItemFromGroup(groupId, id) {
    const group = this.getGroupById(groupId);
    group.items = group.items.filter(item => item.id !== id);
    this.updateGroups();
},
addItemToGroup(groupId, id) {
    this.getGroupById(groupId).items.push({id});
    this.updateGroups();
},    

If items are unique per group you can avoid the using Array.filter and having to create a new array by splicing the item out of the array using Array.splice

removeItemFromGroup(groupId, id) {
    const items = this.getGroupById(groupId).items;
    const itemIdx = items.findIndex(item => item.id === id);
    itemIdx > -1 && items.splice(itemIdx, 1);
    this.updateGroups();
},

Or if items are not unique, or unique only sometimes you could iterate over the array removing them as you encounter them.

removeItemFromGroup(groupId, id) {
    const items = this.getGroupById(groupId).items;
    var i = items.length;
    while (i--) { items[i].id === id && items.splice(i, 1) }
    this.updateGroups();
},

Safer

The next example guards the function from trying to modify items if the group does not exist.

getGroupById(id) { return this.state.groups.find(g => g.id === id) },
updateGroups() { this.setState({groups: this.state.groups}) },
removeItemFromGroup(groupId, id) {
    const group = this.getGroupById(groupId);
    if (group) {
        group.items = group.items.filter(item => item.id !== id);
        this.updateGroups();
    }
},
addItemToGroup(groupId, id) {
    const group = this.getGroupById(groupId);
    if (group) {
        group.items.push({id});
        this.updateGroups();
    }
},   

And just in case you don’t want to duplicate items in a group.

// Only adds items if if it does not already exist in the group.
addItemToGroup(groupId, id) {
    const group = this.getGroupById(groupId);
    if (group && !group.items.some(item => item.id === id)) {
        group.items.push({id});
        this.updateGroups();
    }
}, 

For the remove function, how about using a map and filter instead:

removeItem (groupId, itemId){
    let {groups} = this.state;
    groups = groups.map((group) => {
        if(group.id === groupId){ 
           group.item = group.item.filter(item => item.id !== itemId);
        }
        return group;
    });
    this.setState({groups});
}

Leave a Reply

Your email address will not be published. Required fields are marked *