2

I'm learning Redux from this tutorial and I don't get how the spread operator below works in both the object and array. If ...state returns the same thing, how can it work in both situations? I thought it will just return an array, so it will work inside the SHUTTER_VIDEO_SUCCESS because it'll just spread whatever is inside the state into the new array in addition to the action.videos, but how will this work inside the SELECTED_VIDEO case? There is no key to place it in. The spread operator grabs the array not the key value pair from the default initialState right?

initialState.js

export default {
  images: [],
  videos: []
};

someComponent.js

import initialState from './initialState';
import * as types from 'constants/actionTypes';

export default function ( state = initialState.videos, action ) {
  switch (action.type) {
    case types.SELECTED_VIDEO:
      return { ...state, selectedVideo: action.video }
    case types.SHUTTER_VIDEO_SUCCESS:
      return [...state, action.videos];
    default:
      return state;
  }
}
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
stackjlei
  • 9,485
  • 18
  • 65
  • 113
  • 3
    [`...` 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 21 '17 at 00:34

3 Answers3

12

UPDATE

Spread syntax allows you to spread an array into an object (arrays are technically objects, as is mostly everything in js). When you spread an array into an object, it will add a key: value pair to the object for each array item, where the key is the index and the value is the value stored at that index in the array. For example:

const arr = [1,2,3,4,5]
const obj = { ...arr } // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }

const arr2 = [{ name: 'x' }, { name: 'y' }]
const obj2 = { ...arr2 } // { 0: { name: 'x' }, 1: { name: 'y' } }

You can also spread strings into arrays and objects as well. For arrays, it will behave similarly as String.prototype.split:

const txt = 'abcdefg'
const arr = [...txt] // ['a','b','c','d','e','f', 'g']

For objects, it will split the string by character and assign keys by index:

const obj = { ...txt } // { 0:'a',1:'b',2:'c',3:'d',4:'e',5:'f',6:'g' }

So you may be getting data that sort of works when you spread an array into an object. However, if the example you gave is what you're actually using, you're going to run into problems. See below.

=============

In the case of reducers in redux, when you use the spread syntax with an array it spreads each item from your array into a new array. It's basically the same as using concat:

const arr = [1,2,3]
const arr2 = [4,5,6]
const arr3 = [...arr, ...arr2] // [1,2,3,4,5,6]
// same as arr.concat(arr2)

With an object, the spread syntax spreads key: value pairs from one object into another:

const obj = { a: 1, b: 2, c: 3 }
const newObj = { ...obj, x: 4, y: 5, z: 6 }
// { a: 1, b: 2, c: 3, x: 4, y: 5, z: 6 }

These are two ways to help keep your data immutable in your reducers. The spread syntax copies array items or object keys/values rather than referencing them. If you do any changes in nested objects or objects in arrays, you'll have to take that into account to make sure you get new copies instead of mutated data.

If you have arrays as object keys then you can spread the entire object into a new one and then override individual keys as needed, including keys that are arrays that need updating with spread syntax. For example, an update to your example code:

const initialState = {
  images: [],
  videos: [],
  selectedVideo: ''
}

// you need all of your initialState here, not just one of the keys
export default function ( state = initialState, action ) {
  switch (action.type) {
    case types.SELECTED_VIDEO:
      // spread all the existing data into your new state, replacing only the selectedVideo key
      return {
        ...state,
        selectedVideo: action.video
      }
    case types.SHUTTER_VIDEO_SUCCESS:
      // spread current state into new state, replacing videos with the current state videos and the action videos
      return {
        ...state,
        videos: [...state.videos, ...action.videos]
      }
    default:
      return state;
  }
}

This shows updating a state object and specific keys of that object that are arrays.

In the example you give, you're changing the structure of your state on the fly. It starts as an array, then sometimes returns an array (when SHUTTER_VIDEO_SUCCESS) and sometimes returns an object (when SELECTED_VIDEO). If you want to have a single reducer function, you would not isolate your initialState to just the videos array. You would need to manage all of your state tree manually as shown above. But your reducer should probably not switch the type of data it's sending back depending on an action. That would be an unpredictable mess.

If you want to break each key into a separate reducer, you would have 3 (images, videos and selectedVideo) and use combineReducers to create your state object.

import { combineReducers } from 'redux'
// import your separate reducer functions

export default combineReucers({
  images,
  videos,
  selectedVideos
})

In that case each reducer will be run whenever you dispatch an action to generate the complete state object. But each reducer will only deal with its specific key, not the whole state object. So you would only need array update logic for keys that are arrays, etc.

shadymoses
  • 3,273
  • 1
  • 19
  • 21
  • [`...` 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 21 '17 at 00:35
  • you addressed how spread syntax work with arrays in array and objects in object, but my question was how does it work with arrays in an object – stackjlei May 21 '17 at 23:18
  • 1
    Updated to more specifically address your provided code/questions. – shadymoses May 22 '17 at 00:13
  • Thanks @shadymoses! But when you used my example, you changed the initialState being passed in from `initialState.videos` to `initialState`, so does that mean that the example I gave is wrong and not suppose to work? I see how yours work because your updating an object with an object, but this doesn't address how my example's `SELECTED_VIDEO` spreads an array (initialState.videos) into an obj(new state) – stackjlei May 23 '17 at 17:08
  • Yes, the reducers in your example will produce very problematic state because the data type will be changing from an array to an object with nested properties based on incoming actions. I updated the answer to address spreading an array into an object. I've also addressed it some in my answer, but I'd suggest reading up more on the redux documentation about how to set up reducers. – shadymoses May 23 '17 at 20:52
0

An array is also a key/value-pair but the key is an index. It's using ES6 destructuring and the spread syntax.

Redux docs on the subject

You may also want to read up on ES6 property value shorthand (or whatever it is called):

ES6 Object Literal in Depth

Whenever you find yourself assigning a property value that matches a property name, you can omit the property value, it’s implicit in ES6.

rymdmaskin
  • 508
  • 5
  • 13
  • [`...` 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 21 '17 at 00:34
  • @FelixKling you are right, changed it to "spread syntax", thanks! Didn't actually think about why I was calling it operator, just did it because many others were. – rymdmaskin May 21 '17 at 08:48
  • so what does the `SELECTED_VIDEO` return? how does the state originally being an array get spread into an object? will it then be `{0: [...], selectedVideo: action.video}`? – stackjlei May 21 '17 at 23:20
  • Using a variable name as both key and value is not what will happen in his examples because he's spreading videos into the object literal, not assigning videos to it. It's { videos } vs { ...videos }, which will have very different results. – shadymoses May 29 '17 at 12:29
0

According to the tutorial:

create-react-app comes preinstalled with babel-plugin-transform-object-rest-spread that lets you use the spread (…) operator to copy enumerable properties from one object to another in a succinct way. For context, { …state, videos: action.videos } evaluates to Object.assign({}, state, action.videos).

So, that's not a feature of ES6. It uses a plugin to let you use that feature.

Link: https://babeljs.io/docs/plugins/transform-object-rest-spread/

Eduardo Melo
  • 481
  • 1
  • 6
  • 19