REDUX question about complicated state objects

I was redoing the Redux challenges after a break, and I realized that for complicated states as parameters and outputs, my simple solution to the task in the lesson:

Fill in the body of the reducer function so that if it receives an action of type 'LOGIN' it returns a state object with login set to true . Otherwise, it returns the current state .

the first time around was a clean but brittle solution. (spoiler alert):

const reducer = (state = defaultState, action) => {
  // change code below this line
  return (action.type=='LOGIN' ? {login: true} : state);
  // change code above this line
};

So I decided to approach the problem as if I were getting a state object of unknown complexity, where I was to leave the state parameter untouched and return a new state object with only the assigned change.(second spoiler alert):

const reducer = (state = defaultState, action) => {
  // change code below this line
  if (action.type == "LOGIN"){
    return Object.defineProperty(
      Object.assign({}, state), "login", {value: true}
    );
  }
  return state;
  // change code above this line
};

The code is clearly less concise, but I believe it is clean. Obviously, if I were to have multiple versions of this, I could create a helper function that could take in an object with all the new assignment and assign them to the new state object. My question is: Is there a more elegant way to do this for complex state objects, or is this it?

[UPDATE]: My brain tickled after my post: “But what about shallow copying?” MDN didn’t mention whether Object.assign created a deep copy. I found this article discussing deep copying of Objects using JSON.parse(JSON.stringify(object));. So, let’s say I did that. Same question: Is using this in a helper function that applies Object.defineproperties() to the state object the cleanest and most flexible solution?

I prefer to use a switch statement and the object spread operator. Not sure if FCC challenges accept it, but you can use it in real code with babel.

switch (action.type) {
  case 'LOGIN':
    return { ...state, login: true };
  default:
    return state;
}

https://redux.js.org/recipes/usingobjectspreadoperator

Thanks. Based on this page on spread objects operator it seems that the spread operator desugars into Object.assign, leading to shallow copying. For shallow state objects, the syntax you provided is EXACTLY what I was looking for. If state must be held in deep objects, a helper using the JSON object’s static methods will pick up the slack.

I guess it helps to RTFM. I read the link you sent on one screen with the MDN page for object.assign again and realized there was no reason to apply Object.defineProperties to the result because the call syntax is: Object.assign(target, ...sources). I swear I read it as source before. Plurals matter. :slight_smile:

You are correct. FCC doesn’t let you use ES6+'s Object Spread Initializer syntax [new in ECMAscript 2018], but the object spread operator still works. If you’re curious, here’s my flexible solution for deep-cloning the state and copy key-value pairs onto it:

const reducer = (state = defaultState, action) => {
  // change code below this line
  return action.type == "LOGIN"
    ? Object.assign(...JSON.parse(JSON.stringify(state)), {login: true})
    : state;
  // change code above this line
};

FYI: I’m with you on switch statements if two conditions collide in their handling, but I’ve begun to convert to object lookup tables. In this case, with exactly one condition, I figured if was fine.

It’s not recommended to do deep copies in redux for performance issues. If nothing inside changed, then you just pass it the way it is, if something did change then you can use the object spread operator to copy only what changed.

switch (action.type) {
  case 'LOGIN':
    return { ...state, other_state: { ...state.other_state, login: true } };
  default:
    return state;
}

https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns

1 Like

Thanks for that. I found the “philosophy of Redux” that I was looking for in the section on copying nested data

Obviously, each layer of nesting makes this harder to read, and gives more chances to make mistakes. This is one of several reasons why you are encouraged to keep your state flattened, and compose reducers as much as possible.

In general, when working with redux, we won’t have only one reducer to work with the whole state. We compose multiple reducers, so every reducer worries only with a small section of the store. And as you found out it’s better to keep your store flattened.