Skip to content

date: 2026-06-16 tags: [vue, vitepress, reactivity, footgun] status: active graduated_to:

A ref passed from a <script setup> template arrives UNWRAPPED — .value in the handler silently no-ops

Symptom — The pill filter in a VitePress Vue component (SkillFilter.vue) did nothing on click; only the text search worked, with no error thrown. The act/mode chips appeared active but never actually filtered the cards.

Root cause — In a <script setup> template, top-level refs are auto-unwrapped. So @click="toggle(act, k)" passes act.value (the plain reactive object), not the ref. The handler function toggle(map, key) { map.value[key] = !map.value[key] } then reads map.valueundefined on the unwrapped object — so the mutation hits undefined[key] and the real state never updates. In script scope act.value is still correct, which is why the read side (apply()) worked and masked the bug.

Fix — Mutate the object directly when it arrived from the template; the object inside the ref is still deeply reactive, so the in-place write triggers updates:

js
function toggle(map, key) {
  map[key] = !map[key]
}

(Or use reactive() instead of ref() for objects you hand around from the template.)

Guard — none automatic. Rule of thumb: a function called from a <script setup> template receives refs already unwrapped — never reach for .value on a template-passed argument.