Why We Use Reflect for Proxy Handlers

Valtio Uses It to Cover Rare Use Cases

Hi,

In my previous post, I described the default behavior of a Proxy get handler as "more or less" correct because the implementation was naive. Let’s explore an edge case that breaks a naive implementation.

We’ll focus on the get handler in this post, but the same concept applies to all handlers.

Understanding the Edge Case

Before diving into Proxies, let’s first look at a JavaScript behavior that is important to understand. Consider the following code:

> const base = { count: 1, get doubled() { return this.count * 2 } };
undefined
> const derived = Object.create(base);
undefined
> derived.count = 2;
2
> console.log(derived.doubled);
4
undefined

Although we don’t usually write this kind of code, it demonstrates two key things:

  1. Prototype Chain – The derived object inherits from base.

  2. Getter Behavior – The doubled getter uses this, which refers to the calling object (derived).

Now, let’s see what happens when we introduce a naive Proxy.

> const base = { count: 1, get doubled() { return this.count * 2 } };
undefined
> const naiveProxy = new Proxy(base, { get(target, prop) { return target[prop]; } });
undefined
> const derived = Object.create(naiveProxy);
undefined
> derived.count = 2;
2
> console.log(derived.doubled);
2
undefined

We expected 4, but got 2. This happens because our get handler directly returns base.doubled, rather than respecting the prototype chain. The this inside the doubled getter still refers to base, not derived.

The Solution: Using Reflect.get

How can we fix this? The correct way is to use Reflect.get.

> const base = { count: 1, get doubled() { return this.count * 2 } };
undefined
> const reflectProxy = new Proxy(base, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); } });
undefined
> const derived = Object.create(reflectProxy);
undefined
> derived.count = 2;
2
> console.log(derived.doubled);
4
undefined

Now, it works as expected, just like our original example without using Proxies.

Why Reflect.get Works

In our fixed implementation, the get handler receives an additional argument: receiver. This represents the value of this (derived in our case). When Reflect.get(target, prop, receiver) is used, it ensures that the getter is called with the correct this value, preserving expected behavior.

Best Practice: Always Use Reflect

This is a tricky issue when working with Proxies, but the solution is simple: always use Reflect in Proxy handlers. This is exactly how it is done in Valtio's codebase.

Happy coding.

Reply

or to participate.