Tracking down where a property changes in JavaScript
Without strict enforcement of boundaries, it can be a mystery where changes happen in the state in your JavaScript application (this is a reason tools like Redux are compelling). I was recently investigating a bug in an app where the source of a state change was a mystery. The app keeps track of the count
of videos uploaded. Deleting a video could sometimes cause the count
to decrement twice when it should only decrement once. I would be able to find this bug quickly if I could get a stack trace every time the state was modified.
This isn't a new problem. You may remember the promise of obj.watch
and Object.observe()
before they were deprecated.
Let's talk about some current options.
Proxy
Proxy allows you to create a replacement for your object that wraps the original object. You can define your own set
"trap" to log the stack trace (or break into a debugger, etc.).
example:
let proxy = new Proxy(objectYouWantToObserve, {
set: function(obj, prop, value) {
if (prop === 'thePropertyWeWantToObserve') {
console.trace(`set: ${prop} -> ${value}`)
}
// The default behavior to store the value
obj[prop] = value;
// Indicate success
return true;
}
// you can optionally define an additional trap for `get` if you're also
// interested in tracking reads for the property
});
You would then need to use your proxy
instance wherever you would have used objectYouWantToObserve
.
The downside of this approach is that, depending on your app design, it isn't always convenient to replace references to the original object.
breakOn
Paul Irish's library break-on-access exposes a breakOn
function that will happily invoke the debugger whenever a property is modified (or, optionally, accessed for read).
example:
breakOn(objectYouWantToObserve, "thePropertyWeWantToObserve")
The upside of this approach is that it requires no additional modification to your code. Any changes to thePropertyWeWantToObserve
will invoke the debugger.
I keep break-on-access
in my Chrome snippets and reach for it first when I need to track down a change in and object's top-level property.
What if I need to observe changes in dynamically nested objects?
If you need to observe properties changing in nested objects, break-on-access
won't help you. With some effort, you can use nested Proxy
s by returning a new Proxy
from the Proxy
's set
trap. I'd also consider something like Observable Slim since it allows observing even deeply-nested changes to an object and its usage is similar to that of Proxy
.