I’m having an issue where I want to use an existing array and update one of it’s properties using a for loop. I expect the outer id and the id inside the array to match, so for example: a & a, b & b, c & c. However when I print out my final array, all of the inner ids are set to c.
I don’t understand why this happens?
edit: added expected output
My Code
const accounts = [{ "id": 1, "currency": "USD" }]; const alphaId = ['a', 'b', 'c']; let finalArr = []; for (index in alphaId) { let newAccount = {}; newAccount.id = alphaId[index]; newAccount.accounts = accounts; newAccount.accounts.forEach(acc => { acc.id = alphaId[index]; }); finalArr.push(newAccount); }
Print Out
[ { "id": "a", "accounts": [ { "id": "c", "currency": "USD" } ] }, { "id": "b", "accounts": [ { "id": "c", "currency": "USD" } ] }, { "id": "c", "accounts": [ { "id": "c", "currency": "USD" } ] } ]
Expected Output
[ { "id": "a", "accounts": [ { "id": "a", "currency": "USD" } ] }, { "id": "b", "accounts": [ { "id": "b", "currency": "USD" } ] }, { "id": "c", "accounts": [ { "id": "c", "currency": "USD" } ] } ]
Advertisement
Answer
The problem is here:
newAccount.accounts = accounts;
That just makes the property accounts
on the new object refer to the same array as accounts
, it doesn’t copy the array. So all your code is working on the same array, and on the same object within that array.
I suspect you meant to copy both the array and the objects in it. We can do that with map
, having it return new objects.
newAccount.accounts = accounts.map((account) => ({...account}));
That uses map
, spread syntax (it’s not an operator), and arrow functions. It’s using the concise form of an arrow function (no {
after the arrow) where the body is just an expression. Since what we’re returning is an object literal, which starts with {
, we have to wrap the entire body in ()
so the parser realizes we’re using the concise form. Here’s what it would look like if we used the “verbose” form:
newAccount.accounts = accounts.map((account) => { return {...account}; });
Here’s the minimal updated version, but keep reading:
const accounts = [ { id: 1, currency: "USD", }, ]; const alphaId = ["a", "b", "c"]; let finalArr = []; for (index in alphaId) { let newAccount = {}; newAccount.id = alphaId[index]; newAccount.accounts = accounts.map((account) => ({...account})); newAccount.accounts.forEach((acc) => { acc.id = alphaId[index]; }); finalArr.push(newAccount); } console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; }
That loops through accounts
twice: once to make the copy, and then again to assign the id
. We can do it just once by assigning the id
while making the copy, using map
:
newAccount.accounts = accounts.map((account) => ({...account, id: alphaId[index]}));
const accounts = [ { id: 1, currency: "USD", }, ]; const alphaId = ["a", "b", "c"]; let finalArr = []; for (index in alphaId) { let newAccount = {}; newAccount.id = alphaId[index]; newAccount.accounts = accounts.map((account) => ({...account, id: alphaId[index]})); finalArr.push(newAccount); } console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; }
Some other notes:
for-in
isn’t the correct way to loop through an array (usually), see my answer here for full details of your various options.Since
finalArr
is a mapping ofalphaId
‘s ID values to objects, we can usemap
instead of the loop, handling creating the new accounts within themap
:
Example:
const finalArr = alphaId.map((id) => ({ id, accounts: accounts.map((account) => ({...account, id})), }));
const accounts = [ { id: 1, currency: "USD", }, ]; const alphaId = ["a", "b", "c"]; const finalArr = alphaId.map((id) => ({ id, accounts: accounts.map((account) => ({...account, id})), })); console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; }