0

When someone comments, I want to change the image's state so that the new comment is added to the screen. The state does change, but it doesn't call for a new render because the old state changes along with the new state. I tried using debugger and it seems like line 40 is where the old state changes. I'm super confused because I'm using Object.freeze() and I'm also only using the newState variable I've created and not even touching the old state. Thanks!

import {
  RECEIVE_ALL_IMAGES,
  RECEIVE_ONE_IMAGE,
  REMOVE_IMAGE,
  REMOVE_IMAGES
} from '../actions/image_actions';

import {
  RECEIVE_LIKE,
  REMOVE_LIKE
} from '../actions/like_actions';

import {
  RECEIVE_COMMENT,
  REMOVE_COMMENT
} from '../actions/comment_actions';

const imagesReducer = (oldState = {}, action) => {
  Object.freeze(oldState);
  let newState = Object.assign({}, oldState);

  switch(action.type){
    case RECEIVE_LIKE:
      newState[action.like.imageId].likerIds.push(action.like.userId);
      return newState;
    case REMOVE_LIKE:
      newState[action.like.imageId].likerIds = newState[action.like.imageId].likerIds.filter(id => id !== action.like.userId);
      return newState;
    case RECEIVE_ALL_IMAGES:
      return Object.assign({}, oldState, action.images);
    case RECEIVE_ONE_IMAGE:
      return Object.assign({}, oldState, {[action.image.id]: action.image});
    case REMOVE_IMAGE:
      delete newState[action.image.id];
      return newState;
    case REMOVE_IMAGES:
      return {};
    case RECEIVE_COMMENT:
      if (newState[action.comment.imageId].comments) {
        newState[action.comment.imageId].comments[action.comment.id] = action.comment;
      } else if (newState[action.comment.imageId]) {
        newState[action.comment.imageId].comments = {};
        newState[action.comment.imageId].comments[action.comment.id] = action.comment;
      }
      return newState
    case REMOVE_COMMENT:
      delete newState[action.comment.imageId].comments[action.comment.id]
      return newState
    default:
      return oldState;
  }
};

export default imagesReducer;
Praveen Rao Chavan.G
  • 2,772
  • 3
  • 22
  • 33
Josh Choi
  • 3
  • 3
  • 2
    You have only shallow copied the old state, but you are mutating values deeper in the object. Freezing only applies to the object itself, not the nested objects that are values of the root object's properties. – shamsup Jan 01 '19 at 00:04

1 Answers1

1

You're only freezing and copying the first layer of properties, but you are then changing layers a couple levels deeper than that and those properties are still shared. Below is a snippet that shows what is going on and one way of changing it, but you may want to look at using a library that makes this easier to get right:

const oldState = { 
  id1: { comments: { cid1: 'Comment1' } }, 
  id2: { comments: { cid2: 'Comment2' } }
};

document.getElementById("step1").innerHTML = 'oldState = ' + JSON.stringify(oldState);

Object.freeze(oldState);
const newState = Object.assign({}, oldState);
newState.id1.comments.cid3 = 'Comment3';

document.getElementById("step2").innerHTML = 'oldState = ' + JSON.stringify(oldState) + '<br/>newState = ' + JSON.stringify(newState);

const newStateNoSideEffects = {...oldState};
newStateNoSideEffects.id2 = {...newStateNoSideEffects.id2};
newStateNoSideEffects.id2.comments = {...newStateNoSideEffects.id2.comments, cid4: 'Comment4'};

document.getElementById("step3").innerHTML = 'oldState = ' + JSON.stringify(oldState) + '<br/>newState = ' + JSON.stringify(newState) + '<br/>newStateNoSideEffects = ' + JSON.stringify(newStateNoSideEffects);
<div id="root">
  <div id="step1">
  </div>
  <br/><br/>
  <div id="step2">
  </div>
  <br/><br/>
  <div id="step3">
  </div>
</div>

You will have a similar issue with the likerIds.push.

Here's a related question: Why should I use immutablejs over object.freeze?

Here's an example using immutability-helper which is mentioned in the React docs:

import React from "react";
import ReactDOM from "react-dom";
import update from "immutability-helper";

function App() {
  const oldState = {
    id1: { comments: { cid1: "Comment1" } },
    id2: { comments: { cid2: "Comment2" } }
  };
  const newState = update(oldState, {
    id1: { comments: { $merge: { cid3: "Comment3" } } }
  });

  return (
    <div className="App">
      <div>oldState = {JSON.stringify(oldState)}</div>
      <div>newState = {JSON.stringify(newState)}</div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit jzm1v32659

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198