DEV Community

Hussein Duvigneau
Hussein Duvigneau

Posted on • Edited on

8

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!.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

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.

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay