35

I have a array like this:

users = [{id:1, name:'name1'},{id:2, name:'name2'}]

How could I get a reference to the item {id:2, name:'name2'}, so I can change it is name property, like:

user = get_item(users, 'id', 2);
user.name = "user2 name changed";

console.log(users) will have the result:

[{id:1, name:'name1'},{id:2, name:'user2 name changed'}]

I tried to use Array.filter() function, but it returns a new array instead of a reference to the original array. which I can not use to mutate the original array.

Any idea?

7 Answers 7

68

I tried to use Array.filter() function, but it returns a new array instead of a reference to the original array. which I can not use to mutate the original array.

It returns a new array, but the array's entries are still references to the same objects. So filter is just fine for this.

var filteredUsers = users.filter(function(entry) { return entry.id === 2; });
var item = filteredUsers[0];
item.name = "user2 name updated";
console.log(users[1].name) // "user2 name updated"

The array contains references to the objects, not copies of them. When we do item = users[1], or use filter to get a new array containing a subset of the objects, we get a copy of the object reference, and so the variable now refers to the same object, like this:

                +−−−−−−−−−−+
users−−−−−−−−−−>| (array)  |          
                +−−−−−−−−−−+           +−−−−−−−−−−−−−−−+
                |   0      |−−−−−−−−−−>|    (object)   |
                |          |           +−−−−−−−−−−−−−−−+
                |          |           | id:   1       |
                |          |           | name: "name1" |
                |          |           +−−−−−−−−−−−−−−−+
                |   1      |−−+
                |          |  |
                |          |  |        +−−−−−−−−−−−−−−−+
                |          |  +−−−+−+−>|    (object)   |
                +−−−−−−−−−−+     / /   +−−−−−−−−−−−−−−−+
                                 | |   | id:   2       |
                +−−−−−−−−−−+     | |   | name: "name2" |
filteredUsers−−>| (array)  |     | |   +−−−−−−−−−−−−−−−+
                +−−−−−−−−−−+     | |   
                |   0      |−−−−−+ |
                +−−−−−−−−−−+       |   
                                   |
item−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

Changing the object changes the object, and those changes are visible regardless of which reference to the object you use to look at its properties.

Now if you had an array of primitives:

var a = [1, 2, 3, 4, 5];

...then of course, you can't use the approach above because they aren't objects. In that case, you'd use indexOf to find the index:

var index = a.indexOf(3); // Find the first entry === 3

...and then modify the entry

a[index] = 42;

This also applies to strings (provided they're nice normal string primitives, not things you created via new String(), which there's virtually never a reason to do).

Sign up to request clarification or add additional context in comments.

4 Comments

This problem is I don't know the index,only know the id property value, that's why I need a helper function to find out the item and refer to it
@xiaopang: Oh, I didn't understand that. I'll fix the answer.
@xiaopang: I've edited again to address the primary misunderstanding in your question.
learned something new today...
44

To do it similarly to the accepted answer, but a bit more succinctly, let us use Array.find, like so:

var item = users.find(u => u.id === 2)
item.name = "user2 name updated"
console.log(users[1].name) // "user2 name updated"

Explanations are very similar to what has been provided in the accepted answer.

5 Comments

I believe this is the correct answer, short and easy :)
this doesn't work a = [1,2,3]; -> (3) [1, 2, 3] b = a.find(i => i === 2); -> 2 b = 4; -> 4 a; -> (3) [1, 2, 3]
Matt it will only work on non primitives values. Javascript returns non primitives like objects and arrays by reference.
If you know the item exists, you could just go: users.find(u => u.id === 2).name = "user2 name updated";
find returns a reference, i think this is a bad design pattern, find should return brand new object to respect mutability.
6

You can use findIndex(), for example:

var users = [{id:1, name:'name1'},{id:2, name:'name2'}]
var idx = users.findIndex(item => item.id === 2);
var u = users[idx]; // the reference of user2
u.name = "name changed."; // same as `users[idx].name = "name changed.";`

1 Comment

This method stops searching the moment it finds a single item that matches, which makes it probably more efficient than using filter that will always process the whole array.
6

Similarly to @zing-lee's answer, you can remove one more line of code using find():

var users = [{id:1, name:'name1'},{id:2, name:'name2'}]
var user = users.find(item => item.id === 2)
user.name = 'name change'

From the docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Return value: The value of the first element in the array that satisfies the provided testing function

Because it's an array of Objects (not primitives), the returned value will be a reference to the original object, so modifying a property of the returned object, will update the property of the object that it is referencing.

Good article here explaining values and references in Javasript: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

3 Comments

Funny to mention that if i'm overriding the object found by the fund method with spread operators like this let foundElement = collections.find(element => element.id === someId); foundElement = {..someData}; It wont work
That's expected behaviour. The spread operator works like Object.assign(). It always returns a shallow copy of the object.
Yeah i was not meaning to complain, was just mentioning to others
1

Either

  users[1].name = "xx"

or if you want to search by id as you tried to do in the question:

for (i = 0; i < users.length; i++) {
  if (users[i].id == "2") {
    users[i].name = "xxx";
  }
}

edit you wanted something more advanced, how about using map: (the idea is that you can just change the elements that match your query and create a new array based on it. you can use filter to filter them out)

[{a: 1, b: 2}, {a:3, b:2}].map(function (elm) {
if (elm.b == 2) {
 elm.a += 5;
}
return elm;
});

this returns:

[{a: 6, b: 2}, {a:8, b:2}]

2 Comments

Is there a better way that a loop?
@xiaopang suggestion to use Array.prototype.map. read about it here: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
0

Check if following function solves your problem

Note : Custom objects are always passed by reference to JavaScript function. Thus array of custom object is passed by reference.

function updateUser(targetArray, keyField, targetField, key, new_val) {
var result = -1;
for (var i = 0; i < targetArray.length; i++) {
    if (targetArray[i][keyField] == key) {
        targetArray[i][targetField] = new_val;
        result = i;
        return result;
    }
}
return result;
}

Example

Comments

0

Here is the code which will better explain the scenario. Run the script and will understand.

var arr = [
	{"name": "James", "age": 34},
	{"name": "Peter", "age": 67}
];

var p1 = arr.filter(function(p){return p.name=="James"});
p1[0] = {"name": "James", "age": 50};
console.log(JSON.stringify(arr)); // Won't work

var p2 = arr.filter(function(p){return p.name=="James"});
p2[0].age = 50;
console.log(JSON.stringify(arr)); // Voila... this works

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.