skip to content

Proxy Identity & Collections

reactive(obj) returns a cached Proxy that is not the original object, and Map, Set and array methods are instrumented to track. Interviewers probe identity bugs and why primitives need ref.

part ofVue.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Vue 3, what happens when you pass a number to reactive(), and why do primitive values need ref() instead?

level: juniorimportance: should knowfreq 45%

answer

  1. a Proxy needs an object target
  2. the value comes back unchanged
  3. a development warning
  4. ref boxes the value in .value

basics

~20 s

reactive(42) returns 42 unchanged and warns in development, because a Proxy can only wrap an object. ref() boxes any value in an object whose .value getter and setter do the tracking, so primitives become reactive.

solid answer

~40 s

`reactive()` works by creating a `Proxy`, and a `Proxy` target must be an object. So for a number, string, boolean, `null` or `undefined`, Vue returns the value as is and, in development, warns 'value cannot be made reactive: 42'. The result is a plain value: reassigning the variable changes nothing Vue can see. `ref()` solves this by boxing the value in a ref object whose `.value` getter tracks and whose setter triggers, so `count.value++` is observable; for an object, `ref` stores its reactive proxy in `.value`. `reactive()` also silently returns some objects unwrapped — a `Date`, a frozen object — because it only proxies plain objects, arrays, `Map`, `Set`, `WeakMap` and `WeakSet`.

go deeper

for a junior

Recall that reactive() only works with objects, and that numbers, strings and booleans go in a ref and are read through .value.

for a middle

Explain that a Proxy needs an object target, what reactive() does with a primitive in development, and how ref's .value accessor makes a primitive observable.

for a senior

Recognise non-proxied values in state, such as Dates and frozen objects, as a cause of silent non-updates, and fix them with replacement writes.

for a principal

Settle how the codebase stores dates and immutable snapshots in reactive state so that nobody depends on in-place mutation of values Vue does not proxy.

## Why reactive() cannot hold a primitive Vue 3's `reactive()` returns a **Proxy** around its argument, and JavaScript only lets you create a proxy for an **object**. A number, string, boolean, `bigint`, `symbol`, `null` or `undefined` has no properties to intercept and no identity to wrap. So when `reactive()` receives one: 1. it does not create anything; 2. in development builds it logs a warning of the form `value cannot be made reactive: 42`; 3. it returns the value unchanged. The variable then holds a plain number. `let count = reactive(0); count++` just increments a local variable; no effect ever hears about it. ## How ref() fixes it `ref(0)` creates a small **ref object** with a `.value` accessor: - reading `.value` records the read against the currently running effect; - assigning `.value` compares the new value with the old one and, if different, notifies subscribers. The primitive lives inside an object Vue controls, so changing it is observable. In templates and in reactive objects, refs are unwrapped, which is why `{{ count }}` works without `.value`. For an object argument, `ref()` stores `reactive(obj)` in `.value`, so the contents are deeply reactive as well. | Call | Result | Reactive? | |---|---|---| | `reactive(42)` | `42` plus a dev warning | no | | `reactive('hi')` | `'hi'` plus a dev warning | no | | `ref(42)` | a ref, `.value === 42` | yes, through `.value` | | `reactive({ n: 42 })` | a proxy | yes, per property | | `reactive(new Date())` | the same `Date`, no warning | no | | `reactive(Object.freeze({ n: 1 }))` | the same frozen object, no warning | no | ## Objects that are also returned as-is `reactive()` only builds proxies for these target types: plain objects, arrays, `Map`, `Set`, `WeakMap` and `WeakSet`. For anything else it returns the argument unchanged **without** a warning: - **built-ins such as `Date`, `RegExp` or `Promise`** — their internal state is not reachable through properties; - **non-extensible objects**, including frozen ones; - **objects explicitly marked to be skipped** (that opt-out belongs to the raw-state APIs). The practical consequence: `state.when.setFullYear(2030)` on a `Date` stored in reactive state mutates the date but triggers nothing. Assign a new `Date` (`state.when = new Date(...)`) instead, which is a tracked write on the containing object. ## isReactive as a check `isReactive(reactive(42))` is `false` — there is no proxy. Likewise for the `Date` case. When a value 'does not update', checking `isReactive` or `isProxy` on it quickly shows whether Vue ever wrapped it. ## Why interviewers ask It is a quick screen for the mental model behind Vue 3 reactivity: proxies wrap objects, so primitives need a box. Candidates who have only used `ref` everywhere may never have thought about why; candidates who know the proxy rule also know why `Date` mutations and frozen data do not update the screen.

  • In Vue 3, why does calling setFullYear() on a Date stored in reactive state not update the template?
    `reactive()` does not proxy `Date` objects; reading `state.when` returns the raw `Date`, so its methods mutate it without any trigger. Replacing it with `state.when = new Date(...)` is a write through the containing proxy, which triggers the readers of `when`.
  • What does isReactive() return for the result of reactive(42), and why?
    `false`. No proxy was created — `reactive()` returned the number itself — and `isReactive` checks for a reactive proxy's internal flag, which a number cannot carry.

saying these in an interview costs you the question

  • reactive(0) returns a reactive number you can increment
  • reactive() throws a TypeError when given a primitive
  • reactive() warns for every value it cannot proxy, Dates included
  • ref() turns the primitive into a String or Number wrapper object
  • Mutating a Date inside reactive state updates the template
open as a page

In Vue 3, why is `reactive(obj) === obj` false, and what do repeated reactive() calls on the same object or its proxy return?

level: middleimportance: should knowfreq 50%

basics

~10 s

reactive(obj) returns a Proxy wrapping obj, a different object, so === is false. Vue caches one proxy per target: calling reactive(obj) again returns that same proxy, and reactive(proxy) returns the proxy itself.

open as a page

In Vue 3, how does a reactive Map of sessions track get(), set() and iteration, and what breaks when keys mix raw and proxy objects?

level: middleimportance: should knowfreq 30%

basics

~20 s

Vue 3 swaps a reactive Map's methods for instrumented ones: get and has track one key, iteration and size track the whole map, and set, delete and clear trigger. Keys are matched raw-or-proxy, but storing both forms of one object creates two entries.

open as a page