Array.map / Filter a contigous array and then re-order it to be contiguous again

104 views Asked by At

Using React, I have a list component that uses array.map to render a list of items.

The list items are variegated; every other list item has a different background color which depends on if the id field of the data structure that feeds the list item is even or odd:

  ...

const useStyles = makeStyles((theme) => ({
  even: {
    backgroundColor: theme.palette.background.paper,
  },
  odd: {
    backgroundColor: "#c8c9c7",
  },
}));

...

const classes = useStyles();

...

{!list || list.length < 1 ? (
    <p>You have no assets selected...</p>
  ) : (
    list.map((items) => (
      <ListItem
        className={items.id % 2 === 0 ? classes.even : classes.odd}
        key={items.id}
      >
    ...
      />
    </ListItem>
  ))
)}

Here is an example of the data structure it uses:

{
    {
        "id":0,
        "foo":"This is a bar"
    },
    {
        "id":1,
        "foo":"This is also a bar"
    },
    {
        "id":2,
        "foo":"Yes, this too, is a bar"
    }
}

I need to remove items. Normal javascript.filter produces non contiguous ids as expected:

{
    {
        "id":0,
        "foo":"This is a bar"
    },
    {
        "id":2,
        "foo":"Yes, this too, is a bar"
    }
}

I need them to be contiguous:

{
    {
        "id":0,
        "foo":"This is a bar"
    },
    {
        "id":1,
        "foo":"Yes, this too, is a bar"
    }
}

I have a function that does what I need that needs some tweaking:

  const handleRemoveAsset = (id) => {
    const arrayCopy = [...assetListItems];
     const filteredArray = arrayCopy
       .filter((item) => item.id !== id)

     for (var i=0; i < filteredArray.length; i++) {
       filteredArray[i].id = i;
     }

    setAssetListItems(filteredArray);
  };

This works, but one does not simply for loop using React... I am hoping to use filter and/or map for the entirety of this and not use the for loop that I have.

I read that you can chain filter and map and tried it but couldn't quite work it out. I came up with this:

   const filteredArray = array
      .filter((item) => item.id !== id)
      .map((item, index) => {
           item && item.id ? item.id : index)});

... which fails to compile with - expected an assignment to a function call and instead saw an expression on the line after .map.

Any advice at this point would appreciated, thank you!

2

There are 2 answers

1
Prateek Thapa On BEST ANSWER

You could chain map and filter and return the new object from map which updates the pre-existing id.

[...assetListItems]
    .filter(item => item.id !== id)
    .map((item, index) => ({
      ...item,
      id: index,
    }));

0
Nithish On

I just considered another scenario where if the id is not starting with 0. And if you want the starting id in the resultant array to be as the id of the first object then this is just another way of achieving the expected output.

let data = [{id:0, foo:'This is a bar'},{id:1, foo:'This is also a bar'},{id:2, foo:'Yes, this too, is a bar'}];


const filterItems = (items, id) => {
  let lastPushedId = items[0]?.id;
  return items.filter(item => item.id !== id).map(item => ({
    ...item,
    id: lastPushedId++
  }))
}
console.log(filterItems(data, 1));

//`id` of the first object is `3`
data = [{id:3, foo:'This is a bar'},{id:4, foo:'This is also a bar'},{id:5, foo:'Yes, this too, is a bar'}];
console.log(filterItems(data, 3));
.as-console-wrapper {
  max-height: 100% !important;
}