DEV Community

Hussein Duvigneau
Hussein Duvigneau

Posted on • Updated on

Pro Tip: How to find the accidental mutation

I joined a React project with no immutability library or middleware, but it's too far-gone to make that change now, especially considering it's never actually caused any issues, until this one example today.

In cases where I need a bit more control than simple lodash.cloneDeep, I typically use the {...spread} method to clone an object, and remembering that this only creates a shallow clone, propagate the same method down the tree of modified nested properties (which thankfully never goes very deep), eg:

// there are a dozen variations on how you might do this...

const modifiedObj = {
    ...originalObj,
    list: [
        ...originalObj.list,
        newItem,
    ]
};

Enter fullscreen mode Exit fullscreen mode

In this case though, I'd missed an ancestor somewhere, causing the original object to be mutated, and scanning up and down this 30-line function, I couldn't find where the mutation was happening.

A brainwave called out from deep inside: "Object.freeze!". No, it wasn't a suggestion to freeze this.props.myProperty to prevent the mutation (I just assume that's a very bad idea), but I could use it as a temporary debugging tool.

Remembering that Object.freeze also only works shallowly, I pulled out this deepFreeze() function from the Mozilla docs:

// NB: you may need to apply "use strict" based on your project set-up

function deepFreeze(object) {

  // Retrieve the property names defined on object
  var propNames = Object.getOwnPropertyNames(object);

  // Freeze properties before freezing self

  for (let name of propNames) {
    let value = object[name];

    object[name] = value && typeof value === "object" ?
      deepFreeze(value) : value;
  }

  return Object.freeze(object);
}
Enter fullscreen mode Exit fullscreen mode

Now apply this function to the source of any cloned object you want to debug before the mutation occurs, and the browser console will conveniently throw an error at the exact line where the mutation was being inadvertently attempted:

const onlyShallowCloned = { ...this.props.myProp};
deepFreeze(this.props.myProp);
someDeepManipulation(onlyShallowCloned);
Enter fullscreen mode Exit fullscreen mode

And easy as that, the now-obvious offending line slaps you in the face as you kick yourself: ahh it was there all along!.

Top comments (1)

Collapse
 
aghost7 profile image
Jonathan Boudreau • Edited

You can also use setters to figure out where a mutation on a specific property is coming from:

var person = { name: 'foobar' };

var name = person.name;
Object.defineProperty(person, 'name', {
  set(value) {
    console.trace('Changing to value: %s', value);
    name = value;
  }
  get() {
    return name;
  }
});

This way you don't need to put use strict everywhere to find the culprit.