1

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?

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
zylo
  • 483
  • 7
  • 12
  • 1
    You're not using spread syntax in an array literal here??? That should be a syntax error in ES6 (maybe you have enabled some experimental babel stuff) – Bergi May 13 '17 at 20:00
  • Btw, for indexed values such as your shape collection, you should *always* use an object. – Bergi May 13 '17 at 20:02
  • 1
    Your `renderStates` is an array. Shouldn't you use `[...renderStates]`? – Pankaj Shukla May 13 '17 at 20:10
  • [`...` is not an operator!](https://stackoverflow.com/questions/37151966/what-is-spreadelement-in-ecmascript-documentation-is-it-the-same-as-spread-oper/37152508#37152508) – Felix Kling May 14 '17 at 01:23

4 Answers4

6

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);
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • Use `Object.assign` instead of that `forEach` loop or the nonstandard syntax – Bergi May 13 '17 at 22:53
  • Perfect, thanks! I was thinking you could just spread the values of the `renderStates` array (objects) into an object and their keys would be the indices. – zylo May 15 '17 at 14:52
1

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);
SpenserJ
  • 802
  • 5
  • 13
  • Very nice, but you need to specify 3 parameters as the 3rd is bound to the index, the second, to the element, and the first to the accumulator. Note that this looks correct, at a glance, because the array contains sequential numbers, but is actually off by 1. – Aluan Haddad May 17 '17 at 01:50
  • Thanks @AluanHaddad. – SpenserJ Jun 10 '17 at 16:38
0

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

Trash Can
  • 6,608
  • 5
  • 24
  • 38
0

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);
rofrol
  • 14,438
  • 7
  • 79
  • 77