277

I have an array of objects. I want to find by some field, and then to change it:

var item = {...}
var items = [{id:2}, {id:2}, {id:2}];

var foundItem = items.find(x => x.id == item.id);
foundItem = item;

I want it to change the original object. How? (I don't care if it will be in Lodash too.)

isherwood
  • 58,414
  • 16
  • 114
  • 157
user3712353
  • 3,931
  • 4
  • 19
  • 33
  • Dose your newer object `item` contains a `id` key? or do you mind having the id as well as all the properties from `item` object in the array entry? – Koushik Chatterjee Sep 20 '18 at 10:37

12 Answers12

442

You can use findIndex to find the index in the array of the object and replace it as required:

var item = {...}
var items = [{id:2}, {id:2}, {id:2}];

var foundIndex = items.findIndex(x => x.id == item.id);
items[foundIndex] = item;

This assumes unique IDs. If your IDs are duplicated (as in your example), it's probably better if you use forEach:

items.forEach((element, index) => {
    if(element.id === item.id) {
        items[index] = item;
    }
});
CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
101

My best approach is:

var item = {...}
var items = [{id:2}, {id:2}, {id:2}];

items[items.findIndex(el => el.id === item.id)] = item;

Reference for findIndex

And in case you don't want to replace with new object, but instead to copy the fields of item, you can use Object.assign:

Object.assign(items[items.findIndex(el => el.id === item.id)], item)

as an alternative with .map():

Object.assign(items, items.map(el => el.id === item.id? item : el))

Functional approach:

Don't modify the array, use a new one, so you don't generate side effects

const updatedItems = items.map(el => el.id === item.id ? item : el)

Note

Properly used, references to objects are not lost, so you could even use the original object reference, instead of creating new ones.

const myArr = [{ id: 1 }, { id: 2 }, { id: 9 }];
const [a, b, c] = myArr;
// modify original reference will change object in the array
a.color = 'green';
console.log(myArr[0].color); // outputs 'green'

This issue usually happens when consuming lists from database and then mapping the list to generate HTML content which will modify the elements of the list, and then we need to update the list and send it back to database as a list.

Good news is, references are kept, so you could organize your code to get advantage of it, and think about a list as an Object with identities for free, which are integers from 0 to length -1. So every time you access any property of your Object, do it as list[i], and you don't lose reference, and original object is changed. Keep in mind that this is useful when your source of truth is only one (the Object created), and your app is always consistently consuming the same Object (not fetching several times from database and assigning it to list along the lifespan of the component).

Bad news is that the architecture is wrong, and you should receive an object by ids (dictionary) if this is what you need, something like

{ 
  1232: { id: 1232, ...},
  asdf234asf: { id: 'asdf234asf', ...},
  ...
}

This way, you don't search in arrays, which is resource consuming. You "just access by key in the object", which is instant and performant.

Soldeplata Saketos
  • 3,212
  • 1
  • 25
  • 38
  • 3
    Please link to an English page if the original question was in English. Also, this example assumes the object is always found. – raarts Jul 03 '17 at 10:45
  • 2
    You can always wrap it in a try catch expression then... right? – Soldeplata Saketos Jul 04 '17 at 11:11
  • 2
    And being completely literal to the posts' question, he wants to edit an element in an array. He doesn't want to know if it exists or not, so we assume he already did that before. – Soldeplata Saketos May 11 '18 at 12:58
  • 1
    @SoldeplataSaketos yes, you *could* wrap it in a `try/catch`, but you shouldn't, because not finding the element is not an exceptional case; it's a standard case you should account for by checking the return value of `findIndex` and then only updating the array when the element was found. – Wayne Jun 09 '20 at 04:17
  • @Wayne not finding the item is not an exception. Right. But let's be imaginative. Trying to access a property of an item that is `undefined` *will be an Type Error* and this is what we are expecting here to happen. That's the unexpected exception. We want to modify an item we know it exists (probably it's in a list and user wants to modify it) – Soldeplata Saketos Jan 28 '22 at 10:14
70

One-liner using spread operator.

 const updatedData = originalData.map(x => (x.id === id ? { ...x, updatedField: 1 } : x));
tonymayoral
  • 4,797
  • 2
  • 26
  • 27
40

An other approach is to use splice.

The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.

N.B : In case you're working with reactive frameworks, it will update the "view", your array "knowing" you've updated it.

Answer :

var item = {...}
var items = [{id:2}, {id:2}, {id:2}];

let foundIndex = items.findIndex(element => element.id === item.id)
items.splice(foundIndex, 1, item)

And in case you want to only change a value of an item, you can use find function :

// Retrieve item and assign ref to updatedItem
let updatedItem = items.find((element) => { return element.id === item.id })

// Modify object property
updatedItem.aProp = ds.aProp
Toodoo
  • 8,570
  • 6
  • 35
  • 58
31

Given a changed object and an array:

const item = {...}
let items = [{id:2}, {id:3}, {id:4}];

Update the array with the new object by iterating over the array:

items = items.map(x => (x.id === item.id) ? item : x)
Fellow Stranger
  • 32,129
  • 35
  • 168
  • 232
  • 1
    I think this is the best solution since it is has the best performance since it go over the array only once and also change the reference of the array ,so it will avoid mutable situations – Ohad Sadan Nov 26 '19 at 10:50
  • 1
    @Spencer so the map loops over each item in the `items` array and checks if that item has `id` the same as the `id` of the item in the `const` variable. If it finds one, it maps that item which is `x` to `item` which is the one in the `const` variable, otherwise it keeps the same element `x` in the `items` array. – Dejazmach May 03 '21 at 13:20
  • Where is the match updated in this example? – Ben Racicot Jun 15 '22 at 20:47
7

May be use Filter.

const list = [{id:0}, {id:1}, {id:2}];
let listCopy = [...list];
let filteredDataSource = listCopy.filter((item) => {
       if (item.id === 1) {
           item.id = 12345;
        }

        return item;
    });
console.log(filteredDataSource);

Array [Object { id: 0 }, Object { id: 12345 }, Object { id: 2 }]

katwal-Dipak
  • 3,523
  • 2
  • 23
  • 23
  • I like filter because it allows to create a new array and for this also not existent entries are 'deleted' – pungggi Dec 25 '18 at 17:50
3

Whereas most of the existing answers are great, I would like to include an answer using a traditional for loop, which should also be considered here. The OP requests an answer which is ES5/ES6 compatible, and the traditional for loop applies :)

The problem with using array functions in this scenario, is that they don't mutate objects, but in this case, mutation is a requirement. The performance gain of using a traditional for loop is just a (huge) bonus.

const findThis = 2;
const items = [{id:1, ...}, {id:2, ...}, {id:3, ...}];

for (let i = 0, l = items.length; i < l; ++i) {
  if (items[i].id === findThis) {
    items[i].iAmChanged = true;
    break;
  }
}

Although I am a great fan of array functions, don't let them be the only tool in your toolbox. If the purpose is mutating the array, they are not the best fit.

Jørgen
  • 8,820
  • 9
  • 47
  • 67
3

I don't see this approach in the answers yet, so here's a simple little one liner

let item = {id: 1, new: true};
let items = [{id: 1}, {id: 2}];

let replaced = [item, ...items.filter(i => i.id !== item.id)]

You're just adding the item to the original array filtered of the item you're replacing.

hobberwickey
  • 6,118
  • 4
  • 28
  • 29
1

find and update values in an array using useState and map es6

    const item = {id:5}
    const items = [{id:2}, {id:2}, {id:2}];

    const [list, setList] = useState(items)

    const onChange = (item) => {
        const newList = list.map((el) => {
                if (el.id === item.id) {
                    return item;
                }
                return el;
            });
        setList(newList);
    }
Ivan K.
  • 748
  • 7
  • 11
0

worked for me

let returnPayments = [ ...this.payments ];

returnPayments[this.payments.findIndex(x => x.id == this.payment.id)] = this.payment;
  • 1
    Please don't just paste code. Explain what is being done and how this solves the issue. – Adrian Mole Dec 17 '19 at 15:20
  • 2
    The accepted most upvoted answer is not much different, yet you didn't point towards them updating there answer.. why is that? – li x Mar 03 '20 at 10:08
0

In my case, I wanted to find some element ( and its length) and wanted to add a new element no_of_agents in the object itself. So following did help me

  details.forEach(obj => {
                const length = obj["fleet_ids"].length || 0;
                obj.set("no_of_agents" , length)
            });

Somehow map() returned with other details(like '$__': InternalCache, strictMode, shardval, etc) which was not required,

normalUser
  • 169
  • 1
  • 3
  • 5
-2

You can do like this too

var item = {...}
var items = [{id:2}, {id:2}, {id:2}];

var foundItem = items.filter((x) => x.id ==  item.id).pop();
foundItem = item;

OR

items.filter((x) => x.id ==  item.id).pop()=item;

 
Sarwar Hasan
  • 1,561
  • 2
  • 17
  • 25