Tricky Behavior with Proxy State

A Case in Valtio

Hi,

I’d like to share something I encountered today. A new discussion was posted in the Valtio repository about an unexpected behavior. It turns out to be a kind of limitation—due to the nature of proxies or the way we use them.

A Simple Example Without Proxies

Let’s start with a plain object example:

const o = {};
const x = (o.x ||= {});
console.log(x === o.x); // ---> true

The idea here is to initialize x if it hasn’t been set yet, and use it in either case. As the last line shows, x and o.x refer to the same object.

Now With Proxies

Things get more interesting when we introduce a proxy. Suppose we use a set handler to wrap any assigned value in another proxy:

const p = new Proxy({}, {
  set(target, key, value) {
    target[key] = new Proxy(value, {});
    return true;
  }
});
const y = (p.y ||= {});
console.log(y === p.y); // ---> false

Here’s where it gets tricky: we expect the proxy to behave transparently, but it doesn’t.
The variable y and p.y are not the same object. Why? Because the expression (p.y ||= {}) does not re-access p.y after the assignment. So the value of y is the raw {}, while p.y is the proxied version.

Re-Evaluating the Expression

If we evaluate the same expression again, it behaves as expected:

const y2 = (p.y ||= {});
console.log(y2 === p.y); // ---> true

Now that p.y is no longer undefined, the expression simply returns p.y, which is already wrapped with a proxy.

Final Thoughts

While this behavior is understandable—especially if you know how proxies work—it’s definitely a little tricky. Explaining it without going into proxy internals can be hard.

Happy coding.

Reply

or to participate.