8ctopotamus
8ctopotamus

Reputation: 518

Spread syntax not working as expected with array of objects

I have a react component where I am trying to spread objects into the state in the constructor.

constructor() {
    super()

    const shapesArray = [1, 2, 3]

    let renderStates = shapesArray.map((el, i) => {
        return {['shape'+i]: 'black'}
    })

    this.state = { ...renderStates }
    console.log(this.state)
}

I want to access the colors by doing this.state.shape0, but when I console log this.state, I get this:

enter image description here

instead of Object {shape0: "black", shape1: "black", shape2: "black"}.

What am I doing wrong here?

Upvotes: 1

Views: 5037

Answers (4)

Aluan Haddad
Aluan Haddad

Reputation: 31833

That is because you are spreading an Array into an Object. Arrays are actually objects with (usually) sequential integral strings as their keys. These keys are the indices of the array.

As shown below, map takes an array and produces another array

const shapesArray = [1, 2, 3];

const renderStates = shapesArray.map((el, i) => {
  return {
    ['shape' + i]: 'black'
  };
});

console.log(renderStates);

When spreading into an Object, the value of each own enumerable property in the source Object is added to the target under its respective key. Since the keys of an array are its indices you end up with an Object with a property for each element of the Array. The name of each property is its index in the array.

To achieve what you want, you can use Array.prototype.reduce to build an object from the array with the names created in the mapping process.

const shapesArray = [1, 2, 3];

const renderStates = shapesArray
  .map((el, i) => {
    return {
      ['shape' + i]: 'black'
    };
  })
  .reduce((o, element) => {
    Object.keys(element).forEach(key => o[key] = element[key]);
    return o;
  }, {});

console.log(renderStates);

Of course this itself can be written more elegantly by spreading the object inside of reduce.

const shapesArray = [1, 2, 3];

const renderStates = shapesArray
  .map((el, i) => {
    return {
      ['shape' + i]: 'black'
    };
  })
  .reduce((o, element) => ({...o, ...element}), {});

console.log(renderStates);

Upvotes: 6

rofrol
rofrol

Reputation: 15246

No need to iterate twice over array. Use reduce:

const shapesArray = [1, 2, 3];

const renderStates = shapesArray.reduce((accumulator, i) => {
  accumulator['shape' + i] = 'black';
  return accumulator;
}, {});

console.log(renderStates);

Upvotes: 0

SpenserJ
SpenserJ

Reputation: 802

As an optimization to aluan-haddad's answer, reduce can handle the logic that was in map

const shapesArray = [1, 2, 3];

const renderStates = shapesArray
  .reduce((acc, _, i) => ({
    ...acc,
    ['shape' + i]: 'black',
  }), {});

console.log(renderStates);

Upvotes: 1

Trash Can
Trash Can

Reputation: 6824

renderStates is an array which has integer properties starting from 0 or the array indices if you want to be specific, so {...renderStates} will take each index, and create a mapping from this index to the value corresponding to that index, to achieve what you are looking for, you need to reduce your renderStates array to an object like so

let renderStates = shapesArray.map((el, i) => {
        return {['shape'+i]: 'black'}
    }).reduce((resultObj, currentShape,index) => resultObj['shape'+index] = currentShape['shape'+index]), { });

renderStates is now an object, and you can spread it and it will produce the result you want

Upvotes: 0

Related Questions