Copy an Object with Object.assign- Redux

Hey there can you help me with the code. Whenever I’m running the tests its showing that “Cannot assign to read only property ‘status’ of object ‘#’”
I can’t figure out what’s wrong.


const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      let newObj = state;
      return Object.assign(newObj, {status: action.type});
    default:
      return state;
  }
};

const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};

const store = Redux.createStore(immutableReducer);

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/front-end-libraries/redux/copy-an-object-with-object-assign

1 Like

let newObj = state; - this isn’t a new object, it’s just a reference to the original object. This isn’t specific to what your doing, it’s how JS works.

With Object.assign though, you can assign to a new object, so if you have Object.assign({}, {foo: 1, bar: 2}, {foo: 2}) (note the empty object there) it will merge the object on the right with the the next one, so {foo: 2} over {foo: 1, bar: 2}. That means there’s a new value for foo, so you get {foo: 2, bar: 2}. Then it takes that and merges it with the next one, which is an empty object (so you still end up with {foo: 2, bar: 2} in this example, but if either of the second or third objects had been references, the end result would have been a new object unconnected to them)

tl/dr if you want to create a new object with Object.assign, put an empty object in as the first argument, and it will merge any other objects you give into that new object. It’s what’s called a shallow copy.

7 Likes

I did it as well but it isn’t working

const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      let newObj = Object.assign({}, {status: action.type});
      return newObj;
    default:
      return state;
  }
};

const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};

const store = Redux.createStore(immutableReducer);

So, going from what I said, remenber that you merge right-to-left and you can pass as many objects as you like to assign.

What you’re meant to do is merge {status: 'online'} into your state. Then that merged object gets merged into the new, empty object {}.

At the minute, you’re just merging the {status: 'online'} into a new object, so that’s all you end up with.

2 Likes

Object.assign will copy all the properties from source to target. Basically, after allocating all the properties to the new object, you need to change the status property of the new object to online.

You need to do something like this:
let newObj = Object.assign({},state);
newObj.status=‘online’;
return newObj;

It will not mutate the original state and changes the “status” property of the new object to online.

Happy Coding!!

3 Likes

Just like you said, it does the work

const newObj = Object.assign({}, state ,  {status: 'online'})
return newObj;
7 Likes

True.
It’s even cleaner if we just go:

return Object.assign({}, state, {status: 'online'});
7 Likes

Since we have been given an action, we ought to use that instead of hard coding “online”. I guess the most accurate solution would be:

return Object.assign({}, state, {status: action.type.toLowerCase()});

6 Likes

As others have pointed it out, let newObj = state; won’t copy state to newObj. This is reference assign, not value. So whatever you do to newObj will have impact on state.

My first solution was copy state to some new object, then modify the status property of that new object. Finally, return the new object. But that’s a 3 lines of code:

const newState = Object.assign({}, state);
newState.status = 'online';
return newState;

There’s another way to do this with just one concise line. As the assign() method can take more than 2 arguments, here’s how you can do this with explanation:

return Object.assign({}, state, { status: 'online' });

The first argument, {}, is the initial look of the new object. state is the whole object we want to copy. So if we stop at this point, our new object is an exact copy of the state with just same properties and values.

However, you can provide a third argument as a object with property status as the only one property. This property will override the status property of the new object, making its value to be ‘online’.

I believe we should know and understand all the techniques manipulating object and array if we want to advance further.

Hope that makes sense to you.

3 Likes

return Object.assign({}, state, {status: ‘online’}); is the key here. https://redux.js.org/recipes/usingobjectspreadoperator has a great article with in depth explanations and examples, I encourage everyone with Redux problems to visit this page as it has helped me many times over.
FYI, I was receiving an error when I coded this because I had capped ‘online’ lol. When I read this post, I was reminded to use lower caps. I hope this helps someone out there, happy coding all!

1 Like

I’m confused about why this doesn’t also pass…

return Object.assign({}, {status: 'online'}, state);

Shouldn’t that work just as well as…

return Object.assign({}, state, {status: 'online'});

@SamuelNewhouse
I think it has to do with order of merging into new object.

You are merging in state into {status...} on the first line.

The 2nd line you are merging {status...} into state.

Yeah, I don’t know what I was thinking. It merges right to left, I knew that, but for some reason I wasn’t thinking clearly enough about it. If state is passed last, status gets overwritten by state’s status.

Smart! Nice thinking man :beers: