Rafael Baptista
Rafael Baptista

Reputation: 11499

Why does array.concat(…) not modify the array?

So I've created this jqueryui widget. Its creates a div that I can stream errors into. The widget code looks like this:

$.widget('ui.miniErrorLog', {
   logStart: "<ul>",   // these next 4 elements are actually a bunch more complicated.
   logEnd:   "</ul>",
   errStart: "<li>",
   errEnd:   "</li>",
   content:  "",
   refs:     [],

   _create: function() { $(this.element).addClass( "ui-state-error" ).hide(); },

   clear: function() { 
      this.content = ""; 
      for ( var i in this.refs )
         $( this.refs[i] ).removeClass( "ui-state-error" );
      this.refs = [];
      $(this.element).empty().hide(); 
   }, 

   addError: function( msg, ref ) {
      this.content += this.errStart + msg + this.errEnd; 
      if ( ref ) {
         if ( ref instanceof Array )
            this.refs.concat( ref );
         else
            this.refs.push( ref );
         for ( var i in this.refs )
            $( this.refs[i] ).addClass( "ui-state-error" );
      }
      $(this.element).html( this.logStart + this.content + this.logEnd ).show();
   }, 

   hasError: function()
   {
      if ( this.refs.length )
         return true;
      return false;
   },
});

I can add error messages into it, and references to page elements that is will put into an error state. I use it to validate dialogs. In the "addError" method I can pass in a single id, or an array of ids, like this:

$( "#registerDialogError" ).miniErrorLog( 
   'addError', 
   "Your passwords don't match.", 
   [ "#registerDialogPassword1", "#registerDialogPassword2" ] );

But when I pass in an array of id's it doesn't work. The problem is in the following lines (i think):

if ( ref instanceof Array )
   this.refs.concat( ref );
else
   this.refs.push( ref );

Why doesn't that concat work. this.refs and ref are both arrays. So why doesn't the concat work?

Bonus: am I doing anything else dumb in this widget? It's my first one.

Upvotes: 130

Views: 131966

Answers (8)

Paiman Rasoli
Paiman Rasoli

Reputation: 1214

The concat method doesn't change the original array, you can use array destructuring.

const numbers = [1,2,3,4];
const newNumbers = [5,6,7,8,9];

numbers.push(...newNumbers); // [1,2,3,4,5,6,7,8,9]

Upvotes: 1

ggorlen
ggorlen

Reputation: 57185

Others have mentioned that this.refs.concat(ref); allocates and returns a new array which can be reassigned to the object: this.refs = this.refs.concat(ref);. concat does not modify either argument array.

However, probably more accurate here is to use push, which adds an element to the calling array in-place: this.refs.push(ref); (no reassignment with =--push returns the new array length which is usually ignored).

If you're adding multiple items, push accepts variable arguments, so you can spread an array onto it:

const arr = [0, 1, 2];
arr.push(3); // add one element
console.log(arr) // => [0, 1, 2, 3]
arr.push(4, 5); // add two elements
console.log(arr) // => [0, 1, 2, 3, 4, 5]
const toAdd = [6, 7, 8];
arr.push(...toAdd); // add an array
console.log(arr); // => [0, 1, 2, 3, 4, 5, 6, 7, 8]

concat could create a similar result with reassignment:

let arr = [0, 1, 2];
arr = arr.concat(3); // reassign with one new element
console.log(arr) // => [0, 1, 2, 3]
arr = arr.concat(4, 5); // reassign with two new elements
console.log(arr) // => [0, 1, 2, 3, 4, 5]
const toAdd = [6, 7, 8];
arr = arr.concat(toAdd); // reassign with a new array added
console.log(arr); // => [0, 1, 2, 3, 4, 5, 6, 7, 8]

concat is clearly less optimal for this use case but is useful to know about for others, particularly when immutability is needed (for example, when working with React state).

Another occasion mutating with push is handy is in a function that should mutate its argument:

const addOne = arr => { // contrived example
  arr.push(1);
};
const arr = [];
addOne(arr);
console.log(arr); // => [1] as expected

const addOneBroken = arr => {
  arr = arr.concat(1); // purely local reassignment!
};
addOneBroken(arr);
console.log(arr); // => still [1]

Another option for merging arrays and items is the spread syntax, similar to concat:

let arr = [0, 1, 2];
const toAdd = [3, 4, 5];
const arr1 = [...arr, 3]; // add one item, similar to concat
console.log(arr1); // [0, 1, 2, 3]
const arr2 = [...arr, ...toAdd]; // add an array, similar to concat
console.log(arr2); // [0, 1, 2, 3, 4, 5]
arr = [-1, ...arr, 42, ...toAdd, 6, 7]; // build a new complex array and reassign
console.log(arr); // => [-1, 0, 1, 2, 42, 3, 4, 5, 6, 7]

The above can be done with chained concat calls:

let arr = [0, 1, 2];
const toAdd = [3, 4, 5];
arr = [-1].concat(arr).concat(42).concat(toAdd).concat(6).concat(7);
console.log(arr); // => [-1, 0, 1, 2, 42, 3, 4, 5, 6, 7]

Note that if you have an array you don't want to flatten during a push, just skip the ... spread:

const arr = [0, 1, 2];
const toAdd = [3, 4, 5];
arr.push(toAdd);
console.log(arr); // => [0, 1, 2, [3, 4, 5]]

Upvotes: 2

Andrew S
Andrew S

Reputation: 2987

Just a note, if you really want to have a mutable array when using the concat function (by mutable I mean that it does not create a new array but mutate the existing one) you can reassign the concat function for that array instance. There is what I did when I needed this.

let myArray = [];

myArray.concat= function(  toAdd){
     if(Array.isArray(toAdd)){
        for(let node of toAdd)
             this.push(node);
      }else
        this.push(toAdd);
}

Upvotes: 0

Saurabh Mistry
Saurabh Mistry

Reputation: 13689

you have to re-assign value using = to array , that you want to get concated value

let array1=[1,2,3,4];
let array2=[5,6,7,8];

array1.concat(array2);
console.log('NOT WORK :  array1.concat(array2); =>',array1);

array1= array1.concat(array2);
console.log('WORKING :  array1 = array1.concat(array2); =>',array1);

Upvotes: 16

mewc
mewc

Reputation: 1447

To expand on Konstantin Dinev:

.concat() doesn't add to current object, so this will not work:

foo.bar.concat(otherArray);

This will:

foo.bar = foo.bar.concat(otherArray);

Upvotes: 25

PRATHYUSH P
PRATHYUSH P

Reputation: 175

dataArray = dataArray.concat(array2)

Upvotes: 2

Konstantin Dinev
Konstantin Dinev

Reputation: 34905

Here is the reason why:

Definition and Usage

The concat() method is used to join two or more arrays.

This method does not change the existing arrays, but returns a new array, containing the values of the joined arrays.

You need to assign the result of the concatenation back in the array that you have.

Upvotes: 89

Alcides Queiroz
Alcides Queiroz

Reputation: 9576

The concat method doesn't change the original array, you need to reassign it.

if ( ref instanceof Array )
   this.refs = this.refs.concat( ref );
else
   this.refs.push( ref );

Upvotes: 346

Related Questions