- Daishi Kato's Read the Code
- Posts
- Tricky Behavior with Proxy State
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