2

I read through the redux-actions tutorial, and am confused by their use of (what I believe to be) destructuring. Below is an example (increment & decrement are both functions returned by the createAction function).

const { createAction, handleActions } = window.ReduxActions;

const reducer = handleActions(
  {
    [increment]: state => ({ ...state, counter: state.counter + 1 }),
    [decrement]: state => ({ ...state, counter: state.counter - 1 })
  },
  defaultState
);

Here's another example of this being used:

const { createActions, handleActions, combineActions } = window.ReduxActions;
​
const reducer = handleActions(
  {
    [combineActions(increment, decrement)]: (
      state,
      { payload: { amount } }
    ) => {
      return { ...state, counter: state.counter + amount };
    }
  },
  defaultState
);

Can somebody explain what's happening in those lines? In simplified terms, I just see {[function]: () => ({})}, and don't understand what this does.

thisissami
  • 15,445
  • 16
  • 47
  • 74
  • And I should note: I've looked through the full MDN article on destructuring and found nothing, and don't really know what else to look for. – thisissami Aug 31 '18 at 21:59
  • 4
    That's not destructuring, that's a [computed property name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names) – ibrahim mahrir Aug 31 '18 at 22:02
  • Voted to reopen to explain the difference using a function as a computed property name makes. Can't help wondering whether that performance hit is negligible on low-power devices. – raina77ow Aug 31 '18 at 22:14

1 Answers1

6

That's indeed a computed property name, but with a twist - a function is used as a key, not a string.

It might look confusing until you remember that each function can be safely cast to string - the result is that function's source code. And that's exactly what happens here:

function x() {}
const obj = { [x]: 42 };
console.log( obj[x] ); // 42
console.log( obj[x.toString()] ); // 42, key is the same actually
console.log( Object.keys(obj) );  // ["function x() {}"]

The advantage of such approach is that you don't need to create additional keys - if you have a function reference, you already have one. In fact, you don't even have to have a reference - it's enough to have a function with the same source:

const one = () => '';
const two = () => '';
console.log(one === two); // false apparently 
const fatArrObj = { [one]: 42 } 
fatArrObj[two]; // 42, take that Oxford scholars!!

The disadvantage is that function is cast to string each time it's used as key - a (supposedly minor) performance hit.


To add some fun, this is valid object literal:

{ 
   [null]: null, // access either with [null] or ['null']
   [undefined]: undefined, 
   [{ toString: () => 42 }]: 42 // access with, you guess it, 42 (or '42')
}

... and this one might go into book of weird interview questions:

const increment = (() => { let x = 0; return () => ++x })();
const movingTarget = { toString: increment };
const weirdObjectLiteral = { [movingTarget]: 42 };
console.log( weirdObjectLiteral[movingTarget] ); // undefined
raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • so does that last "weird interview question" bit end up as `undefined` because `movingTarget` is is an object? – thisissami Aug 31 '18 at 22:49
  • What makes this tick is its `toString()` implementation - it returns different value each time it's called. That's why the name, _movingTarget_. ) – raina77ow Aug 31 '18 at 22:52
  • oh - would this be different if we used `[toString]` instead of `toString` as the key? – thisissami Aug 31 '18 at 22:54
  • No, it would just use global `toString` function (defined on `window`) as key. – raina77ow Aug 31 '18 at 22:56
  • Hmm I'm not following. Could you elaborate a bit more? Would using the global `toString` via a computed property name (`[window.toString]` for clarity) work? How about `[toString]` instead of the explicit `window.toString`? I'm confused as to why the `toString` implementation would constantly change.. – thisissami Aug 31 '18 at 23:01
  • 1
    ooooh or is it because of the `()` at the end of the `increment` line that i just noticed? – thisissami Aug 31 '18 at 23:01
  • and then is that because every time you reference `increment`, it'll increment - because that `()` is part of variable? – thisissami Aug 31 '18 at 23:02
  • 1
    this is pretty neat. never knew you could do stuff like this with js!! thanks for sharing :) – thisissami Aug 31 '18 at 23:04
  • could help me understand why the `increment` function runs when we run `console.log( weirdObjectLiteral[movingTarget] );`, but not when we run `console.log( movingTarget );`? I'm having a really hard time wrapping my head around that. – thisissami Sep 01 '18 at 00:01
  • Because `console.log` doesn't use `toString()` for objects, using its own representation mechanism. – raina77ow Sep 01 '18 at 09:21
  • I'm still not fully understanding this, so I'm going to ask a new stackoverflow question. :) I'll tag you in it. – thisissami Sep 01 '18 at 15:49
  • now that i've posted my question (https://stackoverflow.com/questions/52129884/why-does-this-really-tricky-computed-property-name-function-works-the-way-it-doe), i think it finally clicked for me. if we use `[toString]` - we have the `toString` return value in that moment used as the key forever. with `toString`, the return value keeps changing. I still am surprised that this value keeps changing though... does it depend on `Date.now()` or `Math.random()` or anything like that? – thisissami Sep 01 '18 at 16:08