2

I'm fairly new to react and es6 syntax. I am using redux for my react web app, and in my reducer I'd like to be able to push strings into my state.likes

Here is my code:

const i = action.index;
console.log(state);
return [
    ...state.slice(0, i), // before the one we are updating
    {...state[i], likes: state[i].likes+1}, //i want to change this bit so I can push deviceKey
    ...state.slice(i+1), //after one we are updating
  ]

When I console.log(state) I have two objects in an array like this:

[
{
  "code": "AAAAAAA",
  "caption": "blah blah blah",
  "likes": [
    "ABCDEFG",
    "BACDEFG"
  ],
  "id": "1234567890",
  "display_src": "https://pbs.twimg.com/profile_images/1044686525/Get_Some.jpg"
},
{
  "code": "BBBBBBB",
  "caption": "nah fam",
  "likes": [
    "ABCDEFG",
    "BACDEFG",
    "CBADEFG"
  ],
  "id": "0987654321",
  "display_src": 'http://got-crossfit.com/wp-content/uploads/2014/04/get-some-rest.jpg'
}
]

Now, how would I push action.deviceKey into state[i].likes whenever this reducer fires? I tried {...state[i], likes: state[i].likes.push(action.deviceKey)}, but it didn't work.

Also could someone eli5 what the ... notation does? I still don't really understand what the point of that is.

Thanks!

Parkicism
  • 2,415
  • 5
  • 20
  • 21
  • Please read the spread opertator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator – Alex Jun 17 '16 at 20:50
  • 1
    ... will open an array as items in your case. var a = [1,2,3,5,5], var b = [a] will result nested array, but var b = [...a] will result same a – Alex Jun 17 '16 at 20:51
  • `[...state.slice(0, i), Object.assign(state[i], {likes: state[i].likes.concat(action.deviceKey)}), ...state.slice(i+1)]` – Hamms Jun 17 '16 at 20:57
  • @Hamms `Object.assign(state[i], ...` is a mutation of state and not recommended. – Mulan Jun 17 '16 at 21:03
  • oh, right you are; I somehow missed that this was react land :P `[...state.slice(0, i), Object.assign({}, state[i], {likes: state[i].likes.concat(action.deviceKey)}), ...state.slice(i+1)]` should do what ya want – Hamms Jun 17 '16 at 23:28
  • Have a look at [What is SpreadElement in ECMAScript documentation? Is it the same as Spread operator at MDN?](http://stackoverflow.com/q/37151966/218196) – Felix Kling Jun 18 '16 at 01:55

3 Answers3

2

There's actually three different ways that ... can be used. Technically, only one of them is valid official Javascript, but the other two are very common.

All of them are used for making "shallow copies" of objects and arrays. A shallow copy is when you create a new object or array, but inside it contains the exact same items/data/references as the original.

First, ... can be used as an "array spread" operator, to make a shallow copy of an array. This is the only form that is part of the actual Javascript specification so far. It's basically the same as Array.slice().

Second, ... can be used inside the JSX syntax (introduced by React, and also used by other similar view libraries). It makes a shallow copy of an object.

Finally, there is an official proposal to use ... as an "object spread" operator to make shallow copies of objects anywhere in your Javascript code, not just in JSX. That proposal is not yet final, but many people are using it by including a Babel plugin to compile it to the equivalent Object.assign() function call.

For your specific question, I think you need to use the Array.concat() function, which returns a new array reference that includes the new item, rather than the Array.push() function, which inserts the item into the existing array.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Please don't call it an operator (because it's not). `...` is a punctuator that is supported in different contexts (array literals, parameter list, function calls (and proposals)). – Felix Kling Jun 18 '16 at 01:56
2

The spread operator (...) doesn't quite do what you think it does. So according to the ECMAScript definition, it's a way of iterating over an Objects enumerable properties. You've got a pretty bad design of your state though which will become difficult to manage as you add complexity.

Array Spread Operator

This just iterates over an Array as a simpler way of creating new Array's from existing ones. Consider the following:

const old = ['b', 'c'];
const newArr = ['a', ...old, 'd']; // ['a', 'b', 'c', 'd']

Your problem can be solved by doing:

case('ADD_DEVICE_KEY'): {
    return [
        ...state.slice(0, i),
        {...state[i], likes: [...[state[i].likes], action.payload], // We're creating a new array here which is has the values of state[i].likes and also whatever we add to it
        ...state.slice(i+1)
    ]      

** EDIT ** As Felix Kling pointed out in the comments below. This is actually an Array Spread Element.


State Design

It's likely you're going to run into issues with managing your state, it's much easier to store your state as an object.

Let's assume your state refers as user id, think of the ways this would be easier to refer to:

{
    '1234567890': {
        "code": "AAAAAAA",
        "caption": "blah blah blah",
        "likes": [
            "ABCDEFG",
            "BACDEFG"
        ],
        "display_src": "https://pbs.twimg.com/profile_images/1044686525/Get_Some.jpg"
   },
   1234567890: {
        "code": "BBBBBBB",
        "caption": "nah fam",
        "likes": [
            "ABCDEFG",
            "BACDEFG",
            "CBADEFG"
         ],
         "display_src": 'http://got-crossfit.com/wp-content/uploads/2014/04/get-some-rest.jpg'
    }
]

Now, if you want to use that data all you need to do props[userId] rather than (assuming you have some complex logic for finding i consistently elsewhere in a way that is sane and can look up users; for(const user of props) { if (user.id === userId) return user }, the first is cleaner, easier to destructure in child components and just nicer to read. You're letting the JS compiler do that for you, I really can't think of a good reason to store your state as an Array (particularly considering an Array for most purposes is just an Object with numeric keys).

By doing that you easily add the device id by using this:

switch(action.type) {
    case('ADD_DEVICE_ID'): {
        return {
            ...state,
            [action.payload.userId]: Object.assign({}, action.payload.userId, { likes: { deviceId: action.payload.deviceId } })
        }
    }
}

Here we're resetting returning a new object, the rest of the state says the same, including the userId object with the exception of likes; which now has a deviceId field set to whatever we pass in. A much cleaner and easier to rationalise method.

Community
  • 1
  • 1
Alex Deas
  • 21
  • 3
  • It's not an *operator*. It's a syntactic extension for array literals, call expressions and parameter lists. – Felix Kling Jun 18 '16 at 05:31
  • [MDN names it as an operator](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator). It is an Assignment operator that tells the compiler to do something specific, being to iterate over each value in the variable the spread has been used on before continuing with the rest of the Array definition. – Alex Deas Jun 18 '16 at 10:30
  • Well, MDN is wrong. Please see my answer here: http://stackoverflow.com/q/37151966/218196 . It's definitely not an assignment operator. – Felix Kling Jun 18 '16 at 13:19
  • And here the reason why I think it's important to not give the impression that this construct is an operator: http://stackoverflow.com/q/35019557/218196 – Felix Kling Jun 18 '16 at 13:29
0

Using spread operator(...) with Object.assign solves the problem

    return 
        [
          ...state.slice(0,i),  // Copy array till index
          Object.assign({},state[i],{likes:state[i].likes+1}),  // update the index array property
          ...state.slice(i+1)   // Copy after the index
        ]
Ghanshyam Singh
  • 1,361
  • 2
  • 15
  • 27