0

I don't like using map and looping to create a new array from an old array in JavaScript when just one item changes. I have a simple array that has data like this:

const speakerData = [{name: 'joe',id: 101,favorite: true},
                     {name: 'sam',id: 102,favorite: false},
                     {name: 'jon',id: 103,favorite: false}]

And I want to update record 102 to favorite is true (toggle favorite).

I have the following code that works:

const speakerId = 102;
const newSpeakerData = speakerData.map((rec) => {
  if (rec.id === speakerId) {
    rec.favorite = !rec.favorite;
    return rec;
  } else {
    return rec;
  }
});

I want something like what I have below but it obviously does not work.

const speakerRec = speakerData.find(a=>a.id === speakerId);
speakerRec.favorite = !speakerRec.favorite;
const newSpeakerData = [{...speakerData},speakerRec]

Is there a clever one line I can make this happen with ES7?

Here is the answer I was looking for that @adiga put in the comments below.

const speakerId = parseInt(e.target.attributes['data-sessionid'].value);
const index = speakerData.findIndex((a) => a.id === speakerId);
const newSpeakerData = Object.assign([...speakerData], {
  [index]: { ...speakerData[index], favorite: !speakerData[index].favorite }
});
Pete
  • 3,111
  • 6
  • 20
  • 43
  • Intead of `find`, use `findIndex` like: `const newSpeakerData = [...speakerData]; const index = speakerData.findIndex(a=>a.id === speakerId); newSpeakerData[index] = ; setState(..)` – adiga Dec 24 '19 at 16:12
  • is the part I'm having trouble with – Pete Dec 24 '19 at 16:43
  • Pete your question is alright to me, and was upvoted. There are many people that doesn't understand how hard can be to communicate and reacts eagerly with downvoting. As far i can see on your questions, you need to modify an item of a collection, so using find is OK as it returns a reference to the object you need to modify, and using that reference you can modified any object's fields. But also, you need to create a brand new collection containing all the elements plus the new one modified? or i'm understanding wrong? [BTW: using a map for modifying an array's item is not a common practice] – Victor Dec 24 '19 at 17:34
  • 1
    @Victor Did you upvote to outweigh the downvote on the question, or because (hover over the upvote button) "_This question shows research effort; it is useful and clear_"? Your "_or i'm understanding wrong?_" suggests that you don't really find it crystal clear. Upvoting to balance out a downvote [is highly inappropriate](https://meta.stackoverflow.com/a/311408). – Ivar Dec 24 '19 at 18:03
  • 1
    "_but it obviously does not work_" What doesn't work @Pete? The first two lines of what you show [seem to do exactly what you are asking for](https://jsfiddle.net/ymertaxc/). – Ivar Dec 24 '19 at 18:14
  • I'm confused about why this has the 'immutability' tag on it. Are you trying to manipulate an existing object in place in the array or create a new array with new objects? – Jim Perris Dec 24 '19 at 18:16
  • note that in your three lines code, you may fail if id is not found (which would not have occured by nesting) – grodzi Dec 24 '19 at 19:16
  • 1
    I'm looking for a more language rich solution than using find or findrec. If it's not possible to use the spread operator I understand that but I've seen other examples where it seems possible using syntax similar to my suggestion of [...,newrec]. Thank you to whoever re-opened this as well as whoever upvoted. I upvote all the time and I don't believe you have to have a appropriate reason. – Pete Dec 24 '19 at 19:47
  • @Ivar, this does not work: `const newSpeakerData = [{...speakerData},speakerRec]` – Pete Dec 24 '19 at 19:49
  • "*I don't like using map and looping to create a new array from an old array*" - what exactly do you not like about it? It seems to do exactly what you want. Are you concerned about readability? Then use a helper function. Or about performance? Something else? – Bergi Dec 24 '19 at 20:22
  • Notice that `rec.favorite = !rec.favorite` is not immutable. – Bergi Dec 24 '19 at 20:24
  • @Bergi, my aim is to minimize nesting and if I can do it with the spread operator and parameters to that, I can minimize the nesting map introduces. this post here on SO has something similar to what I want but have not been able to morph it to my problem. https://stackoverflow.com/questions/45673783/replace-array-entry-with-spread-syntax-in-one-line-of-code – Pete Dec 24 '19 at 21:12
  • `const newSpeakerData = [...speakerData], index = speakerData.findIndex(a=> a.id === speakerId), obj = speakerData[index]; newSpeakerData[index] = { ...obj, favorite: !obj.favorite }; setState(..)` – adiga Dec 25 '19 at 04:13
  • I don't know why this is reopned. The duplicate explains how to do this without mapping over every object – adiga Dec 25 '19 at 04:15
  • @adiga, as the op here, my question never asked how to solve the problem without using .map. That came up in the discussion. I'm still looking for a way to do this using the spread operator similar to how the solution in https://stackoverflow.com/questions/45673783/replace-array-entry-with-spread-syntax-in-one-line-of-code was worked out. That is why the question was re-opened because it has not been answered yet. – Pete Dec 25 '19 at 13:52
  • `const index = speakerData.findIndex(a=> a.id === speakerId); this.setState({ speakerData: Object.assign([...speakerData], { [index]: {...speakerData[index], favorite: !speakerData[index].favorite}})})` – adiga Dec 25 '19 at 13:57
  • Thanks @adiga. That is exactly the answer I was looking for and I've updated my question to include the answer. If you want to past that code into an answer, I'll happily accept it. Also, the syntax I still don't get is this `[index]:` but it does work. – Pete Dec 25 '19 at 21:18
  • You can refer to [this answer](https://stackoverflow.com/a/45673826/3082296) – adiga Dec 26 '19 at 05:34

2 Answers2

0

One option is, while mapping, also put favorite: id === speakerId ? !favorite : favorite into the returned object:

const speakerData = [{
    name: 'joe',
    id: 101,
    favorite: true
  },
  {
    name: 'sam',
    id: 102,
    favorite: false
  },
  {
    name: 'jon',
    id: 103,
    favorite: false
  }
];
const speakerId = 102;
const mappedData = speakerData.map(({ favorite, name, id }) => ({
  name,
  id,
  favorite: id === speakerId ? !favorite : favorite
}));
console.log(mappedData);

Doesn't look that pretty, but that's what I'd prefer, and I'm doubtful there's a better way.

Or, you can squish it onto one line if you really have to (but you probably shouldn't, it's too hard to read):

const mappedData = speakerData.map(({ favorite, name, id }) => ({ name, id, favorite: id === speakerId ? !favorite : favorite }));
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
0

You pretty much need the map for keeping the array immutable, but you might get away with a neater syntax by using the conditional operator instead of if/else statements:

const speakerId = 102;
const newSpeakerData = speakerData.map((rec) =>
  rec.id === speakerId
    ? {...rec, favorite: !rec.favorite}
    : rec
);

(The spread syntax usage also makes sure not to mutate the rec object).

If this is still too long or has too much indentation, you can of course write a helper function instead to use like

const matchSpeakerId = rec => rec.id === 102;
const newSpeakerData = mapWhere(speakerData, matchSpeakerId, rec => ({...rec, favorite: !rec.favorite}));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375