Skip to the solution
HardFrontend10 min read

What are shallowRef and shallowReactive?

469 views

The problem

Question presented to candidate: "You see shallowRef and shallowReactive in a code review. Explain what they do differently from ref and reactive, and what happens when someone mutates a nested property on them."

What a strong answer should cover:

  • ref and reactive are deep: nested objects are wrapped in proxies on access, so a write at any depth triggers updates.
  • shallowRef tracks only .value assignment; the value itself is stored as-is (not proxied), so nested mutations are invisible.
  • shallowReactive tracks only its own top-level properties; nested objects stay raw and refs are not unwrapped.
  • Assigning the same object back to a shallowRef does not trigger, because Vue compares with Object.is.
  • triggerRef() forces a shallowRef update after an in-place mutation; it does nothing for a shallowReactive.
  • Typical uses: large data you replace wholesale, external or immutable state, and holding values that must stay unproxied.

Clarifying questions expected:

  • "Is the data replaced as a whole, or edited in place?"
  • "Is this about rendering performance, or about keeping an external object unproxied?"

Code / implementation expected: Yes β€” a short demo showing a nested mutation that does not re-render and a top-level replacement that does.

  • shallowref

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 10 sections

Read the code

Vueref vs shallowRef vs shallowReactive: which writes re-render
<script setup>
import { defineComponent, h, ref, shallowRef, shallowReactive } from "vue";

// Each card is its own component, so a re-render of one card
// cannot "accidentally" refresh another card.
function makeCard(title, create, replace) {
  return defineComponent({
    setup() {
      const state = create();
      let renders = 0;
      const user = () => ("value" in state ? state.value.user : state.user);
      return () => {
        renders++;
        return h("section", { class: "card" }, [
          h("h4", title),
          h("p", { class: "out" }, `name: ${user().name} | renders: ${renders}`),
          h("button", { class: "nested", onClick: () => (user().name += "!") }, "mutate nested"),
          h("button", { class: "replace", onClick: () => replace(state) }, "replace top level"),
        ]);
      };
    },
  });
}

const DeepRef = makeCard(
  "ref (deep)",
  () => ref({ user: { name: "Ada" } }),
  (s) => (s.value = { user: { name: s.value.user.name } })
);
const ShallowRefCard = makeCard(
  "shallowRef",
  () => shallowRef({ user: { name: "Ada" } }),
  (s) => (s.value = { user: { name: s.value.user.name } })
);
const ShallowReactiveCard = makeCard(
  "shallowReactive",
  () => shallowReactive({ user: { name: "Ada" } }),
  (s) => (s.user = { name: s.user.name })
);
</script>

<template>
  <p>Click "mutate nested" on each card, then "replace top level".</p>
  <DeepRef />
  <ShallowRefCard />
  <ShallowReactiveCard />
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 84 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextHard

How does the Vue compiler optimize templates (static hoisting, patch flags, block tree)?

Continue