# Vue from Zero - The Framework That Lets You Mutate

> How Vue actually works - reactive data that tracks its own readers, templates that re-render themselves, and single-file components - taught from the reactivity model up.


---

# Vue from Zero - The Framework That Lets You Mutate

Vue has a reputation as the approachable frontend framework, and the reputation is earned - but
"approachable" gets mistaken for "shallow," and then developers use Vue for a year without knowing
*why* changing `count.value` updates the screen. That gap stays fine right up until the day
reactivity silently stops working and nothing in your mental model explains it.

This guide builds the model properly: what Vue's reactivity actually is (a tracking system built on
proxies), why mutation is the *intended* way to change data here, and how templates, components, and
watchers all hang off that one system. If you've read our React guide, you'll get contrast notes
where the two philosophies split; if you haven't, this guide stands on its own.

## How to read this

- **In a panic right now?** Jump to [Phase 7: When Vue Breaks](07-when-it-breaks.md) - the
  cheat-card at the top covers the classic "reactivity stopped working" mysteries.
- **Want it to finally make sense?** Read in order. Phase 3 (reactivity) is the load-bearing wall.

## The phases

1. **[What Vue Actually Is](01-what-vue-actually-is.md)** - reactive data + templates that follow
   it, and the anatomy of a `.vue` file.
2. **[Templates That React](02-templates-that-react.md)** - `{{ }}`, `:bind`, `@click`, `v-if`,
   `v-for`, and the two-way `v-model`.
3. **[Reactivity for Real](03-reactivity-for-real.md)** - `ref`, `reactive`, `computed`, and the
   traps that silently disconnect your data from the screen.
4. **[Components: Props, Events, and v-model](04-components-props-events.md)** - building blocks
   that talk both directions.
5. **[Slots and Composition](05-slots-and-composition.md)** - components that wrap content, and
   composables that package logic.
6. **[Watchers, Lifecycle, and Fetching](06-watchers-lifecycle-fetching.md)** - reacting to changes
   and talking to servers.
7. **[When Vue Breaks](07-when-it-breaks.md)** - lost reactivity, forgotten `.value`, mutated
   props, and key mistakes, decoded.
8. **[Where to Go Next](08-where-to-go-next.md)** - Router, Pinia, Nuxt, and what to skip for now.

> Deliberately deferred to follow-up guides: Nuxt and server-side rendering, TypeScript-heavy
> component patterns, transitions/animation, and testing. The reactivity model comes first;
> everything else is built on it.


---

# What Vue Actually Is

Strip away the ecosystem and Vue is two ideas holding hands:

1. **Reactive data** - JavaScript objects that *know when they're read and when they're changed*.
2. **Templates** - HTML that declares which data it depends on, and re-renders itself when that
   data changes.

You change the data - directly, with ordinary assignments - and every piece of the page that used
that data updates. No manual DOM work, and no "you must not mutate" rules either. That second part
is worth dwelling on, because it's Vue's defining bet.

## The five-line version

```html
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>

<template>
  <button @click="count++">Clicked {{ count }} times</button>
</template>
```

*What just happened:* `ref(0)` created a reactive container holding `0`. The template reads it
(`{{ count }}`), which - and this is the trick - **registers the template as a dependent of
`count`**. The click runs `count++`, a plain mutation. The container notices the write, checks who
depends on it, and re-renders exactly that button's text. Nobody called a render function; nobody
told the DOM anything.

💡 **Key point:** Vue's model is a **dependency-tracking system**. Reading reactive data inside a
template (or a computed, or a watcher - later phases) subscribes the reader to that data. Writing
the data notifies the subscribers. Your job is to read and write naturally; the bookkeeping is the
framework's.

```mermaid
flowchart LR
  T[template renders] -->|reads count| S[(reactive data)]
  S -->|subscribes reader| T
  M["count++ (mutation)"] -->|write notifies| S
  S -->|re-render dependents| T
```

📝 **Terminology:** if you've read our React guide - this is the philosophical fork in the road.
React re-runs your whole component and diffs the output, so it needs *new objects* to detect change
(mutation breaks it). Vue tracks reads at property level, so it knows precisely what changed and
*wants* you to mutate. Neither is wrong; they're different answers to "how does the framework find
out?" Vue's answer: the data itself reports.

## The .vue file: one component, three blocks

That snippet above is a **single-file component** (SFC) - Vue's signature format. One `.vue` file
holds a component's logic, markup, and styling:

```html
<script setup>
// logic: state, functions, imports
import { ref } from 'vue';
const name = ref('world');
</script>

<template>
  <!-- markup: HTML plus Vue's template syntax -->
  <p class="greeting">Hello, {{ name }}!</p>
</template>

<style scoped>
/* styling: 'scoped' = these rules apply to THIS component only */
.greeting { color: teal; }
</style>
```

Three things to notice, one per block:

- **`<script setup>`** - the `setup` attribute is modern Vue's shorthand: everything declared at
  the top level (variables, functions, imports) is automatically available to the template. Older
  tutorials show `export default { data() {...}, methods: {...} }` - that's the **Options API**,
  the previous style. It still works, but this guide (like current Vue docs) teaches the
  **Composition API** with `<script setup>`; if a tutorial nests things under `data()` and
  `methods:`, it's teaching the older dialect.
- **`<template>`** - looks like HTML because it mostly is. It's compiled, not string-interpolated:
  Vue's build step turns it into a render function that knows exactly which dynamic parts depend on
  which data.
- **`<style scoped>`** - `scoped` rewrites the selectors so they can't leak out and hit other
  components. Component-local CSS, no naming conventions required.

## Booting an app

```console
$ npm create vue@latest my-app
✔ Project name: … my-app
✔ Add TypeScript? … No
✔ Add Vue Router for Single Page Application development? … No
✔ Add Pinia for state management? … No

$ cd my-app && npm install && npm run dev

  VITE ready in 487 ms
  ➜  Local:   http://localhost:5173/
```

*What just happened:* the official scaffold (`create-vue`) built a Vite project - Vue and Vite come
from the same team, and the dev experience shows it. The prompts offer Router and Pinia; saying no
is right while learning - both get their moment in phase 8.

The whole app starts from one mount call in `src/main.js`:

```js
import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
```

*What just happened:* `createApp` builds an application instance around your root component and
`mount` hands it a DOM node to own. Everything inside `#app` is Vue's from here on - the same
"one root node, framework takes over" contract as every modern frontend framework.

## Recap

1. Vue = reactive data + templates subscribed to it. Reads register dependencies; writes notify
   them.
2. Mutation is the intended API: `count++` and `user.name = 'Ada'` are how change happens here.
3. A `.vue` SFC holds logic (`<script setup>`), markup (`<template>`), and styles
   (`<style scoped>`) for one component.
4. Composition API with `script setup` is the current dialect; Options API (`data()`, `methods:`)
   is the older one you'll meet in legacy code.
5. `createApp(App).mount('#app')` hands Vue its patch of the page.

```quiz
[
  {
    "q": "In Vue, how does the framework know which parts of the page to update when data changes?",
    "choices": [
      "It re-renders the whole app and diffs the result",
      "Reads are tracked: whatever read the data during render is registered as its dependent, and writes notify exactly those dependents",
      "It polls data for changes on every animation frame",
      "The developer lists dependencies for each template block"
    ],
    "answer": 1,
    "why": [
      "That's closer to React's model - Vue's tracking is finer-grained: it knows which property each template depends on.",
      null,
      "No polling exists - reactive objects report writes at the moment of assignment.",
      "Templates never declare dependencies; using a value in a template IS the registration."
    ],
    "explain": "Vue's reactivity is a subscription system: reading reactive data subscribes the reader, writing publishes to the subscribers. That's why plain mutation works."
  },
  {
    "q": "A tutorial's component has export default with data() and methods: sections. What are you looking at?",
    "choices": [
      "A syntax error - Vue components must use script setup",
      "The Options API - Vue's older component style, still supported",
      "A React component ported to Vue",
      "Server-side Vue, which uses a different syntax"
    ],
    "answer": 1,
    "why": [
      "It's fully valid Vue - just the previous idiom, not an error.",
      null,
      "React has neither data() nor methods: - this shape is distinctly Vue, just older Vue.",
      "Server-side rendering uses the same component syntax; the dialect split is Options vs Composition, not server vs client."
    ],
    "explain": "Vue has two component dialects. Options API organizes by option type (data, methods, computed); Composition API with script setup organizes by feature. New code and current docs use the latter."
  }
]
```


---

# Templates That React

Vue templates are HTML with a small set of superpowers called **directives** - attributes starting
with `v-` that tell the compiler "this part is dynamic." There are fewer than you'd think, and five
of them cover essentially all daily work. This phase is that five, plus the gotcha each one carries.

## Text and attributes: {{ }} and :

```html
<script setup>
import { ref } from 'vue';
const product = ref({ name: 'Kettle', imageUrl: '/kettle.jpg', inStock: true });
</script>

<template>
  <h2>{{ product.name }}</h2>                     <!-- text interpolation -->
  <img :src="product.imageUrl" :alt="product.name" />  <!-- attribute binding -->
  <button :disabled="!product.inStock">Buy</button>
</template>
```

*What just happened:* `{{ }}` drops a reactive expression into text. For attributes, mustaches
don't work - `src="{{ url }}"` is a beginner classic that sets the literal string. Attributes bind
with the `:` prefix (shorthand for `v-bind:`): `:src="product.imageUrl"` means "this attribute's
value is a JavaScript expression, keep it in sync."

Everything inside `{{ }}` and `:` is a real expression - `{{ price * quantity }}`,
`:class="{ active: isActive }"` (that object form toggles CSS classes by boolean, and you'll use it
weekly). Statements don't fit (`{{ if (x) ... }}` is a compile error); anything beyond a simple
expression belongs in a `computed` (phase 3).

## Events: @

```html
<button @click="count++">Add one</button>
<button @click="addToCart(product.id)">Add to cart</button>
<form @submit.prevent="save">...</form>
```

*What just happened:* `@click` (shorthand for `v-on:click`) attaches a handler - either an inline
expression or a function to call. The third line shows a **modifier**: `.prevent` is
`event.preventDefault()` as a suffix, so form-submit handlers don't start with boilerplate. The
modifiers you'll actually use: `.prevent`, `.stop` (stopPropagation), and key modifiers like
`@keyup.enter="search"`.

📝 **Terminology:** contrast note for React readers - there's no "pass a function, don't call it"
trap here. `@click="addToCart(product.id)"` compiles to a handler that runs the expression *on
click*, because templates are compiled, not evaluated inline. One classic bug that doesn't exist in
this dialect.

## Branching: v-if and v-show

```html
<p v-if="cart.length === 0">Your cart is empty.</p>
<p v-else-if="cart.length < 10">{{ cart.length }} items.</p>
<p v-else>Bulk order!</p>

<div v-show="detailsOpen">...expensive details panel...</div>
```

Two ways to hide things, with a real difference:

- **`v-if`** removes the element from the DOM entirely. False means gone - not rendered, listeners
  detached, child component state destroyed (it unmounts, same semantics as conditional rendering
  anywhere).
- **`v-show`** always renders but toggles `display: none`. The element stays alive, state intact.

The rule of thumb: `v-if` for branches that rarely change or shouldn't exist when false (auth-gated
UI, error states); `v-show` for things toggled frequently where you want the flip to be instant and
the state preserved (tabs, dropdowns).

## Lists: v-for and the :key contract

```html
<ul>
  <li v-for="todo in todos" :key="todo.id">
    {{ todo.text }}
  </li>
</ul>
```

*What just happened:* one `<li>` per array item. The `:key` is not decoration: when the array
changes, Vue matches old and new items *by key* to decide what moved versus what's new - reuse the
moved, build the new, delete the gone. Give it a stable identity (`todo.id`), never the array index
for lists that can reorder or delete: index keys make Vue reuse the wrong element's DOM (and any
state attached to it, like a checkbox or an input) for a different item.

⚠️ **Gotcha:** `v-if` and `v-for` on the *same element* is a lint error in Vue 3 - `v-if` runs
first and can't see the loop variable, which surprises everyone. Filter in a computed instead
(`doneTodos` from phase 3), or nest: `<template v-for="...">` wrapping an inner `v-if`. The
`<template>` tag renders nothing itself - it's the grouping element for exactly these cases.

## Two-way forms: v-model

The pattern every form needs - value flows into the input, keystrokes flow back into state - has a
dedicated directive:

```html
<script setup>
import { ref } from 'vue';
const email = ref('');
const agreed = ref(false);
const plan = ref('free');
</script>

<template>
  <input v-model="email" type="email" placeholder="you@example.com" />
  <label><input v-model="agreed" type="checkbox" /> I agree</label>
  <select v-model="plan">
    <option value="free">Free</option>
    <option value="pro">Pro</option>
  </select>
  <p>Signing up {{ email || '…' }} for the {{ plan }} plan.</p>
</template>
```

*What just happened:* `v-model="email"` is sugar for `:value="email"` plus `@input` writing back -
binding in both directions with one attribute. It adapts per element: checkboxes bind the boolean
`checked`, selects bind the chosen `value`. Modifiers ride along here too: `v-model.number` casts
the string input to a number, `v-model.trim` strips whitespace, `v-model.lazy` syncs on change
instead of every keystroke.

Contrast note: this is the controlled-input pattern with the wiring pre-soldered. The trade is the
usual one for sugar - less typing, and one more layer to see through when something misbehaves.
Phase 4 opens the lid and shows exactly what `v-model` expands to, because the same directive works
on your own components once you know its parts.

## Recap

1. `{{ expr }}` for text; `:attr="expr"` for attributes - mustaches never work inside attributes.
2. `@event="handler-or-expression"`, with modifiers (`.prevent`, `.stop`, `@keyup.enter`) for the
   boilerplate.
3. `v-if` unmounts (state dies); `v-show` hides with CSS (state survives). Choose by toggle
   frequency and whether state should persist.
4. `v-for` needs `:key` with stable identity - index keys corrupt row state on reorder/delete.
5. `v-model` = value-down + event-up in one attribute; `.number`, `.trim`, `.lazy` refine it.

```quiz
[
  {
    "q": "A tab panel with a search input inside it is toggled with v-if. Every time the user switches away and back, the typed search is gone. Why?",
    "choices": [
      "v-if clears form fields for security reasons",
      "False v-if unmounts the element - its state is destroyed, not hidden; v-show would preserve it",
      "The input is missing a :key attribute",
      "v-model resets refs when the template re-renders"
    ],
    "answer": 1,
    "why": [
      "There's no security policy involved - unmounting is just what v-if means.",
      null,
      ":key identifies items in lists; a lone input doesn't need one and it wouldn't prevent the unmount.",
      "v-model faithfully reflects the ref - but this input's DOM (and the component state around it) ceased to exist."
    ],
    "explain": "v-if = element removed from the DOM, state and all. For frequently-toggled UI whose state should survive, v-show hides with display:none and keeps everything alive."
  },
  {
    "q": "Why does <img src=\"{{ imageUrl }}\"> show a broken image?",
    "choices": [
      "Mustache syntax doesn't work inside attributes - the browser received the literal text as the URL; it needs :src=\"imageUrl\"",
      "The image path must be absolute in Vue templates",
      "img tags need v-model for dynamic sources",
      "The ref wasn't unwrapped with .value in the template"
    ],
    "answer": 0,
    "why": [
      null,
      "Relative paths work fine once the binding is real.",
      "v-model is for two-way form binding - an image source only flows one way, via :src.",
      "Templates auto-unwrap refs - .value is a script-side concern (phase 3)."
    ],
    "explain": "Interpolation is for text content only. Attributes bind with the : prefix, which evaluates the expression and keeps the attribute in sync."
  }
]
```


---

# Reactivity for Real

Phase 1 promised that Vue's data "knows when it's read and written." This phase is how - because
the day reactivity silently stops working (and that day comes for everyone), the difference between
a five-minute fix and a lost evening is knowing what the tracking system physically is.

## The mechanism under everything

Vue's reactivity is built on JavaScript **proxies** - objects that wrap your data and intercept
every property access. Read `state.count` and the proxy's `get` trap fires: *"the thing currently
rendering just read `count` - subscribe it."* Write `state.count = 5` and the `set` trap fires:
*"notify everyone subscribed to `count`."* That's the whole magic: interception at the property
level.

Hold that picture, because both of this phase's traps are cases where the interception gets
*bypassed*.

## ref: any value, one container

```js
import { ref } from 'vue';

const count = ref(0);
const user = ref({ name: 'Ada' });

console.log(count.value);   // 0 - in script, the value lives on .value
count.value++;              // write through .value = tracked
user.value.name = 'Grace';  // nested mutation is tracked too
```

**What it actually is.** A `ref` is a container object with a single reactive property: `.value`.
Why a container at all? Because JavaScript can't intercept a plain variable - `let count = 0` has
no property to trap. Wrapping the value in an object gives the proxy machinery something to hold
onto. `.value` is not ceremony; it *is* the reactive access point.

**In templates, `.value` disappears.** Vue auto-unwraps top-level refs in templates:
`{{ count }}`, not `{{ count.value }}`. Convenient, and the source of the most common beginner
error - in the script, forgetting `.value`:

```js
const count = ref(0);
count = count + 1;     // ✗ TypeError: Assignment to constant variable (best case)
count.value = count.value + 1;  // ✓
```

Best case, `const` saves you with a loud error. Worst case (with `let`), you've replaced the
container with a plain number, and reactivity is simply gone - no error, no updates, ever again.

## reactive: proxy an object in place

```js
import { reactive } from 'vue';

const form = reactive({ name: '', email: '', attempts: 0 });
form.attempts++;          // no .value - the object itself is the proxy
```

`reactive` wraps an object (only objects - not numbers or strings) so its properties are tracked
directly, no `.value` anywhere. Reads naturally, mutates naturally. So why doesn't everyone use it
for everything? Because of the biggest trap in Vue:

## The destructuring trap

```js
const form = reactive({ name: '', email: '' });

const { name } = form;      // ✗ name is now a plain, dead string
let email = form.email;     // ✗ same problem

// later...
name = 'Ada';               // updates nothing, tracked by nothing
```

**Why this breaks.** Destructuring *copies the value out* of the proxy. The copy is an ordinary
string with no connection to the tracking system - reading it registers nothing, and you can't even
write back through it. The proxy only intercepts access *through itself* (`form.name`); the moment
a primitive leaves the proxy, it's inert. The same applies to passing `form.name` into a function,
or spreading: `{ ...form }` produces a completely non-reactive object.

The fixes, in order of preference:

```js
// 1. Just keep the object together: form.name everywhere. Simplest, usually right.

// 2. Need to hand pieces around? toRefs converts each property into a linked ref:
import { toRefs } from 'vue';
const { name, email } = toRefs(form);   // ✓ refs, connected to form
name.value = 'Ada';                     // updates form.name, tracked
```

💡 **Key point:** this trap is why many Vue teams standardize on `ref` for everything, objects
included. Refs survive being passed around *as the container* - you hand over the box, not the
contents, and access through `.value` always goes through the tracking. The practical rule: **`ref`
by default; `reactive` for a group of fields you'll always keep together and never destructure.**
Both are correct Vue; the rule optimizes for the mistake humans actually make.

## computed: derived values with a memory

```js
import { ref, computed } from 'vue';

const todos = ref([
  { text: 'Learn reactivity', done: true },
  { text: 'Trust the proxy', done: false },
]);

const remaining = computed(() => todos.value.filter(t => !t.done).length);
```

**What it actually is.** A read-only ref whose value is computed by your function - with two
properties a plain function call doesn't have:

- **It's reactive both directions.** The computation reads `todos.value`, so `remaining` subscribes
  to `todos`; anything reading `remaining` subscribes to *it*. Change a todo and the chain
  re-evaluates: data → computed → template.
- **It caches.** The function re-runs only when a dependency changed. Ten template references to
  `{{ remaining }}` cost one computation. A method called from the template runs every render.

The smell it exists to fix: state that duplicates other state. If you're maintaining a
`remainingCount` ref by hand next to `todos`, you've created two sources of truth that *will*
disagree someday. If it can be computed, `computed` it.

⚠️ **Gotcha:** computeds are for *deriving*, not *doing*. No mutations, no fetches, no
`Math.random()` inside - the function may run at unpredictable times (or not at all, if cached), so
side effects in a computed produce heisenbugs. Side effects belong in handlers and watchers
(phase 6).

## Recap

1. Reactivity = proxies intercepting property access: reads subscribe, writes notify.
2. `ref` wraps any value; script access is `.value`, templates auto-unwrap. The container is the
   reactive unit - pass the box, not the contents.
3. `reactive` proxies objects in place - and destructuring/spreading copies dead values out.
   `toRefs` when pieces must travel.
4. Default to `ref`; use `reactive` for keep-together field groups.
5. `computed` = cached, chainable derivation. Derive, don't duplicate - and no side effects inside.

```quiz
[
  {
    "q": "const { name } = reactive({ name: 'Ada' }) - and updates to name no longer affect the UI. Why?",
    "choices": [
      "reactive only works on nested objects, not strings",
      "Destructuring copied a plain value out of the proxy - access no longer goes through the tracking system",
      "The template needs {{ name.value }} to unwrap it",
      "reactive objects are read-only outside the component that created them"
    ],
    "answer": 1,
    "why": [
      "reactive tracks properties of any type - through the proxy; the string was fine until it was copied out.",
      null,
      "There's no ref here to unwrap - the copy is a dead primitive, and no syntax revives it.",
      "reactive objects are freely writable anywhere - through the proxy."
    ],
    "explain": "The proxy can only intercept access through itself. Destructuring hands you the contents without the container - use form.name directly, or toRefs(form) to get linked refs."
  },
  {
    "q": "When does a computed re-run its function?",
    "choices": [
      "On every template render that references it",
      "Only when one of the reactive values it read last time has changed",
      "On a timer that Vue manages",
      "Every time any state anywhere in the component changes"
    ],
    "answer": 1,
    "why": [
      "That describes a method call in a template - the exact cost computed's cache avoids.",
      null,
      "Nothing is polled; the dependency graph triggers re-evaluation.",
      "Only its own dependencies matter - unrelated state changes don't touch it."
    ],
    "explain": "A computed tracks what it reads and caches its result. Its dependencies changing invalidates the cache; the next read re-computes. That's why it beats a method for anything referenced repeatedly."
  },
  {
    "q": "Why does ref exist at all - why can't Vue just track let count = 0?",
    "choices": [
      "It could, but ref makes the code more readable",
      "JavaScript offers no way to intercept reads and writes of a plain local variable - only property access on an object can be trapped",
      "Refs are needed for TypeScript support",
      "Plain variables would be too slow to track"
    ],
    "answer": 1,
    "why": [
      "It's a language constraint, not a style choice - there is no API for observing a local binding.",
      null,
      "TypeScript types refs nicely, but the container exists for the proxy machinery, not the type system.",
      "Speed isn't the issue - there's simply no interception point on a bare variable."
    ],
    "explain": "Proxies trap property access on objects. A bare variable has no properties to trap, so Vue wraps the value in a one-property container: .value is the interception point."
  }
]
```


---

# Components: Props, Events, and v-model

An SFC becomes a reusable building block the moment it can take data in and report changes out.
Vue's answer is the same shape as every component system - props down, events up - with the
directions *declared* rather than implied. And once you know both halves, `v-model` on components
stops being magic: it's the two halves in a trench coat.

## Props: declared inputs

```html
<!-- ProductCard.vue -->
<script setup>
const props = defineProps({
  name: { type: String, required: true },
  price: { type: Number, required: true },
  inStock: { type: Boolean, default: true },
});
</script>

<template>
  <article :class="{ dimmed: !inStock }">
    <h3>{{ name }}</h3>
    <p>{{ (price / 100).toFixed(2) }} €</p>
  </article>
</template>
```

```html
<!-- used from a parent -->
<ProductCard name="Kettle" :price="4900" :in-stock="false" />
```

*What just happened:* `defineProps` declares what this component accepts - with runtime types,
required flags, and defaults. Vue warns in the console when a parent passes the wrong type or skips
a required prop: your component's contract, enforced during development. Note the casing at the
call site: camelCase props are written kebab-case in templates (`inStock` → `:in-stock`).

⚠️ **Gotcha:** `:price="4900"` versus `price="4900"` matters exactly like phase 2's binding rule -
without the colon you're passing the *string* `"4900"`, and the type check will tell you so. The
colon means "expression"; quotes alone mean "literal string."

**Props are one-way.** Assigning to a prop (`props.name = 'x'`) logs a warning and doesn't
propagate - a child editing its inputs would make data flow untraceable, the same reasoning as
every one-way framework. The child's move when it *wants* a change is the other half:

## Emits: declared outputs

```html
<!-- ProductCard.vue -->
<script setup>
defineProps({ name: String, price: Number });
const emit = defineEmits(['add-to-cart']);
</script>

<template>
  <article>
    <h3>{{ name }}</h3>
    <button @click="emit('add-to-cart', 1)">Add</button>
  </article>
</template>
```

```html
<!-- parent -->
<ProductCard name="Kettle" :price="4900" @add-to-cart="qty => cart.add('kettle', qty)" />
```

*What just happened:* the child *emits* a named event with a payload; the parent listens with the
same `@` syntax used for DOM events. The child doesn't know what adding to a cart means - it
announces intent, the parent decides. `defineEmits` documents the component's outputs the way
`defineProps` documents inputs; one file tells a new teammate the entire interface.

```mermaid
flowchart TD
  P[Parent - owns cart state] -->|"props: name, price"| C[ProductCard]
  C -->|"emit('add-to-cart', qty)"| P
```

## v-model on components: the trench coat opens

Phase 2 used `v-model` on inputs. On your own components, it's the props/emits pattern with agreed
names - `modelValue` in, `update:modelValue` out:

```html
<!-- these two lines are identical -->
<StarRating v-model="rating" />
<StarRating :modelValue="rating" @update:modelValue="v => rating = v" />
```

So a component supports `v-model` by implementing that contract:

```html
<!-- StarRating.vue -->
<script setup>
defineProps({ modelValue: Number });
const emit = defineEmits(['update:modelValue']);
</script>

<template>
  <span>
    <button v-for="n in 5" :key="n" @click="emit('update:modelValue', n)">
      {{ n <= modelValue ? '★' : '☆' }}
    </button>
  </span>
</template>
```

*What just happened:* the component never stores the rating - it displays the prop and emits the
requested change; the parent's `v-model` writes it back into the parent's ref. State stays in one
place (the parent), and the child stays a pure view of it. This is "lifting state up" as a
first-class framework convention.

📝 **Terminology:** current Vue wraps this whole contract in one macro - `defineModel()` declares
the prop and emit pair and hands you a writable ref. Sugar over sugar; knowing the
`modelValue`/`update:modelValue` layer underneath is what lets you debug either spelling.

## Where state should live

The same question every component system asks, with the same answer:

- **One component cares** → a `ref` inside it.
- **Siblings need it** → lift it to the common parent; props down, emits up.
- **The whole app needs it** (theme, user, cart) → `provide`/`inject` (phase 5) or a store
  (phase 8).

The anti-pattern to name early: **copying a prop into a local ref** so you can mutate it
(`const localName = ref(props.name)`). Now there are two truths, and the copy goes stale the moment
the parent updates. If the child needs to change it, that's the emit pattern; if the child needs a
transformed view of it, that's a `computed` reading the prop.

## Recap

1. `defineProps` declares typed, defaulted inputs; camelCase in script, kebab-case in templates;
   `:` for expressions.
2. Props are one-way - children request changes by emitting, parents decide.
3. `defineEmits` + `emit('event', payload)` is the child-to-parent channel, listened to with `@`.
4. Component `v-model` = `modelValue` prop + `update:modelValue` emit (or the `defineModel` sugar).
5. Don't copy props into local state - derive with computed, or emit to change the source.

```quiz
[
  {
    "q": "A child component copies a prop into a ref (const local = ref(props.title)) and edits that. What goes wrong?",
    "choices": [
      "Vue throws a warning about mutating props",
      "The copy disconnects from the parent - later parent updates never reach it, and the child's edits reach nobody",
      "The ref fails because props aren't reactive",
      "Nothing - this is the recommended pattern"
    ],
    "answer": 1,
    "why": [
      "No warning fires - the prop itself was never assigned; that's what makes this bug quiet.",
      null,
      "Props are reactive - but ref(props.title) captures the current value once, not a live link.",
      "It's the canonical two-sources-of-truth mistake: emit to change it, or compute a derived view."
    ],
    "explain": "ref(props.title) snapshots the value at setup time. The parent and child now hold independent copies that drift. Emit for changes; computed for transformations."
  },
  {
    "q": "What is <Toggle v-model=\"enabled\" /> shorthand for?",
    "choices": [
      "A two-way proxy that lets the child write the parent's ref directly",
      ":modelValue=\"enabled\" plus @update:modelValue writing back into enabled",
      "A shared reactive object both components mutate",
      ":value=\"enabled\" plus @input, like on a DOM input"
    ],
    "answer": 1,
    "why": [
      "The child never touches the parent's ref - it emits a request; the parent's generated handler does the writing.",
      null,
      "No shared object exists - state stays in the parent; the child is a view of it.",
      "That's the expansion on native inputs; components use the modelValue/update:modelValue pair."
    ],
    "explain": "Component v-model is convention, not magic: a modelValue prop in, an update:modelValue event out, and the parent's v-model wires the event back into its own state."
  }
]
```


---

# Slots and Composition

Props carry *data* into a component. But some components need to receive *markup* - a Card that
frames whatever you put in it, a Modal that wraps any content. And some logic - "track the mouse,"
"fetch with loading state" - wants to be reused across components that share no markup at all. Vue
has one tool for each: slots for markup, composables for logic. Together they're how Vue codebases
stay DRY without inheritance trees.

## Slots: the component owns the frame, you own the contents

```html
<!-- Card.vue -->
<script setup>
defineProps({ title: String });
</script>

<template>
  <section class="card">
    <h2>{{ title }}</h2>
    <slot>Nothing here yet.</slot>   <!-- caller's content lands here; text = fallback -->
  </section>
</template>
```

```html
<!-- caller -->
<Card title="Danger zone">
  <p>Deleting your account is permanent.</p>
  <button @click="confirmDelete">Delete</button>
</Card>
```

*What just happened:* everything between `<Card>` and `</Card>` replaced the `<slot>` outlet. The
Card controls structure and styling; the caller controls contents - including live, reactive
contents with their own handlers. Text inside `<slot>` is the fallback when a caller passes
nothing.

**Named slots** give a component several outlets:

```html
<!-- PageLayout.vue -->
<template>
  <header><slot name="header" /></header>
  <main><slot /></main>                    <!-- the unnamed one is "default" -->
  <footer><slot name="footer" /></footer>
</template>
```

```html
<PageLayout>
  <template #header><h1>Orders</h1></template>
  Order list goes here.
  <template #footer><small>Updated hourly.</small></template>
</PageLayout>
```

The `#header` syntax (short for `v-slot:header`) targets a `<template>` block at a named outlet.
One layout component, every page slotting its parts in - this is the pattern Vue Router's layouts
and every component library's dialogs are built from.

⚠️ **Gotcha:** slot content is compiled in the **parent's** scope. Inside
`<template #header>` you can read the parent's state, but *not* the child's - a slot is a window
into the child's markup, not its data. When the child must share data back into the slot (a list
component handing each item to caller-provided markup), that's **scoped slots**:
`<slot :item="item" />` in the child, `<template #default="{ item }">` in the parent. Worth
recognizing on sight; a follow-up guide gives it a full treatment.

## Composables: logic as a function

The Composition API's payoff beyond organization: **reactive logic can live in a plain function.**
Convention: name starts with `use`, lives in `src/composables/`.

```js
// composables/useFetch.js
import { ref } from 'vue';

export function useFetch(getUrl) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);

  async function load() {
    loading.value = true;
    error.value = null;
    try {
      const res = await fetch(getUrl());
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      data.value = await res.json();
    } catch (e) {
      error.value = e;
    } finally {
      loading.value = false;
    }
  }

  return { data, error, loading, load };
}
```

```html
<!-- any component -->
<script setup>
import { useFetch } from '@/composables/useFetch';
const { data: orders, loading, error, load } = useFetch(() => '/api/orders');
load();
</script>

<template>
  <p v-if="loading">Loading…</p>
  <p v-else-if="error">Couldn't load orders.</p>
  <ul v-else><li v-for="o in orders" :key="o.id">{{ o.ref }}</li></ul>
</template>
```

*What just happened:* the fetch machinery - three refs and their choreography - moved into a
function any component can call. Each call creates *fresh* refs (no shared state between callers,
unless you deliberately hoist refs to module scope). The component keeps only what's unique to it:
which URL, and what the states look like.

💡 **Key point:** a composable is not a framework feature - it's a plain function that happens to
create refs and computeds. That's the Composition API's whole design: because reactivity lives in
importable functions (`ref`, `computed`, `watch`) rather than in component options, *your* logic
can be an importable function too. Notice the destructuring is safe here: `data` and `loading` are
refs - containers, per phase 3 - so handing them out preserves reactivity.

## provide / inject: skipping the middle layers

Props work level to level. For values needed across many levels - theme, current user, locale -
threading a prop through five components that don't use it gets old. A parent can **provide**;
any descendant can **inject**:

```js
// App.vue (or any ancestor)
import { provide, ref } from 'vue';
const theme = ref('dark');
provide('theme', theme);       // provide the ref itself, not theme.value
```

```js
// any component, any depth below
import { inject } from 'vue';
const theme = inject('theme', ref('light'));   // second arg: default if nobody provided
```

*What just happened:* the descendant received the *same ref* the ancestor provided - reactive, so
theme changes propagate to every injector. Provide the ref, not its unwrapped value: `provide('theme',
theme.value)` hands out a frozen string, and phase 3 told you why that's dead on arrival.

The judgment call is the same as any broadcast mechanism: `provide`/`inject` shines for
widely-read, rarely-changed app context, and turns into hide-and-seek if you route everyday
parent-child data through it. Props are greppable; injections need the reader to know the key
exists. Default to props; inject for genuine cross-cutting context.

## Recap

1. Slots receive markup: default slot for one outlet, named slots (`#header`) for several,
   fallback content inside `<slot>`.
2. Slot content sees the parent's scope; scoped slots are the child-hands-data-back variant.
3. Composables = reactive logic in plain `use*` functions; fresh refs per call, importable
   anywhere, safely destructurable because refs are containers.
4. `provide`/`inject` carries reactive context past intermediate layers - provide the ref itself.
5. Props for everyday flow; injection for app-wide context; slots whenever a component should
   frame content it doesn't own.

```quiz
[
  {
    "q": "Inside <template #header> passed to a layout component, you reference one of the layout's internal refs and get undefined. Why?",
    "choices": [
      "Named slot templates can only contain static HTML",
      "Slot content compiles in the parent's scope - the child's data isn't visible unless the child exposes it via a scoped slot",
      "The ref needed .value in the template",
      "provide/inject is required to use data inside slots"
    ],
    "answer": 1,
    "why": [
      "Slot templates are fully dynamic - handlers, bindings, all of it; the constraint is whose data they see.",
      null,
      "Templates auto-unwrap refs - and no unwrapping trick grants access to another component's scope.",
      "Injection shares app context; the standard channel for child-to-slot data is the scoped slot."
    ],
    "explain": "A slot is the caller's markup shown inside the child's frame - it evaluates against the caller's scope. Children share data into slots explicitly: <slot :item=\"item\">."
  },
  {
    "q": "Two components both call useFetch(). Do they share the data ref?",
    "choices": [
      "Yes - composables create module-level state shared by all callers",
      "No - each call runs the function fresh and creates its own refs",
      "Only if both components are children of the same parent",
      "Yes, unless the composable is marked as scoped"
    ],
    "answer": 1,
    "why": [
      "Refs created inside the function body are per-call; sharing happens only when refs are deliberately hoisted to module scope outside the function.",
      null,
      "The component tree has no bearing on it - function-call semantics do.",
      "There's no scoped marker - the function's own structure decides what's shared."
    ],
    "explain": "A composable is a plain function. Each call executes its body and makes fresh refs - isolation by default, shared state only if you hoist refs outside the function on purpose."
  }
]
```


---

# Watchers, Lifecycle, and Fetching

Computed handles "this value follows from that value." But some reactions to change aren't values -
they're *actions*: refetch when the selected id changes, save a draft when the form changes, start a
timer when the component appears and stop it when it leaves. Actions in response to change are
**watchers**; actions tied to a component's existence are **lifecycle hooks**. Together they're
Vue's side-effect toolkit - powerful, and the part of Vue most often used where `computed` should
have been.

## watch: when this changes, do that

```js
import { ref, watch } from 'vue';

const selectedId = ref(1);
const product = ref(null);

watch(selectedId, async (newId, oldId) => {
  product.value = null;                      // show loading state
  product.value = await fetchProduct(newId);
});
```

*What just happened:* `watch` subscribes to a reactive source (here a ref) and runs the callback on
change, with new and old values. The classic use is exactly this: a *side effect* (network call) in
response to a *data change* (selection).

The source argument has a shape rule that bites everyone once:

```js
watch(selectedId, ...)          // ✓ a ref: watch the container
watch(() => props.userId, ...)  // ✓ a getter: watch an expression/property
watch(props.userId, ...)        // ✗ passes today's VALUE (a number) - nothing to subscribe to
```

Passing `props.userId` evaluates immediately to a plain number - dead, per phase 3, and Vue warns
about an invalid watch source. Anything that isn't itself a ref/reactive object gets wrapped in a
getter function.

Two options cover most real needs:

```js
watch(source, callback, { immediate: true });  // also run once right now (initial fetch + refetch in one)
watch(form, callback, { deep: true });         // fire on nested mutations inside an object
```

`deep` exists because watching a `reactive` object or a ref-of-object only fires by default when
the *identity* changes or (for `reactive`) properties are directly touched by the watcher's
tracking - mutating `form.address.city` deep inside won't trigger a shallow watcher on a ref.
`deep: true` traverses everything (at a cost proportional to the object's size); often the sharper
tool is watching the specific field: `watch(() => form.address.city, ...)`.

📝 **Terminology:** `watchEffect(fn)` is the auto-tracked sibling: it runs immediately and re-runs
whenever *anything it read* changes - no explicit source list. Convenient for "keep these things in
sync" effects; less explicit about when it fires. Reach for `watch` when you care about *which*
change triggers the action (and want old/new values); `watchEffect` for fire-and-forget syncing.

## Don't watch what you can compute

The most common watcher in beginner Vue is a hand-rolled computed:

```js
// ✗ a second source of truth, updated by machinery
const fullName = ref('');
watch([firstName, lastName], () => {
  fullName.value = `${firstName.value} ${lastName.value}`;
});

// ✓ a derivation
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
```

💡 **Key point:** if the reaction to data changing is *producing another value*, that's `computed`.
If it's *doing something* - fetching, saving, logging, touching a browser API - that's `watch`. The
computed version can't be stale, can't fire in the wrong order, and deletes three lines.

## Lifecycle: onMounted and onUnmounted

A component's script runs before any DOM exists. Code that needs the real page - measuring an
element, starting an interval, third-party libraries attaching to a node - waits for **mount**:

```html
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const seconds = ref(0);
let timer;

onMounted(() => {
  timer = setInterval(() => seconds.value++, 1000);
});

onUnmounted(() => {
  clearInterval(timer);   // whatever mount starts, unmount stops
});
</script>
```

*What just happened:* `onMounted` fired after the component's DOM landed on the page;
`onUnmounted` fired when it left (a `v-if` went false, the route changed). The pairing is the
discipline: an interval, listener, or subscription started at mount and not stopped at unmount
keeps running against a dead component - the classic leak, stacking a new timer on every remount.

The full hook family exists (`onUpdated`, `onBeforeMount`, ...) but these two carry nearly all
real usage. If you're reaching for `onUpdated`, first ask whether a `watch` on the specific data
says what you mean more precisely.

## The standard fetch shapes

Initial load - await inside `onMounted` (or just call an async function from setup):

```html
<script setup>
import { ref, onMounted } from 'vue';
const orders = ref(null);
const error = ref(null);

onMounted(async () => {
  try {
    const res = await fetch('/api/orders');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    orders.value = await res.json();
  } catch (e) {
    error.value = e;
  }
});
</script>

<template>
  <p v-if="error">Couldn't load orders.</p>
  <p v-else-if="!orders">Loading…</p>
  <ul v-else><li v-for="o in orders" :key="o.id">{{ o.ref }}</li></ul>
</template>
```

Refetch-on-change - the `watch` with `immediate` from above, which handles both first load and
every change of the source. And when the same shape repeats across components, phase 5 already
showed the endgame: fold it into a `useFetch` composable.

⚠️ **Gotcha:** the refetch watcher has the same stale-response race every framework meets: the
user flips from product 1 to product 2, responses arrive out of order, and 1 overwrites 2. The
watcher callback receives a third argument, `onCleanup`, made for exactly this:

```js
watch(selectedId, async (id, _, onCleanup) => {
  let cancelled = false;
  onCleanup(() => { cancelled = true; });   // runs before the NEXT callback fires
  const data = await fetchProduct(id);
  if (!cancelled) product.value = data;
});
```

## Recap

1. `watch(source, cb)` = side effects on specific changes; sources are refs or getters, never
   `props.x` bare.
2. `immediate` for run-now-and-on-change; `deep` (or better: a targeted getter) for nested
   mutations; `watchEffect` when auto-tracking reads is clearer.
3. Producing a value → `computed`; doing a thing → `watch`. Never maintain derived state by
   watcher.
4. `onMounted` for DOM-dependent setup; `onUnmounted` mirrors it - every start gets a stop.
5. Refetch watchers need `onCleanup` for the out-of-order response race.

```quiz
[
  {
    "q": "watch(props.userId, cb) warns about an invalid source and never fires. Why?",
    "choices": [
      "Props can't be watched, only refs can",
      "The expression evaluated to a plain number immediately - watch needs a ref or a getter like () => props.userId",
      "The callback must be async",
      "Watching props requires deep: true"
    ],
    "answer": 1,
    "why": [
      "Props watch fine - through a getter that defers the read.",
      null,
      "Sync callbacks are fine; the source, not the callback, is broken.",
      "deep governs nested objects - a primitive prop needs deferral, not depth."
    ],
    "explain": "Arguments evaluate before the call: props.userId is already just 7 by the time watch sees it. A getter hands watch the recipe instead of the result."
  },
  {
    "q": "A component starts a setInterval in onMounted with no onUnmounted. The component sits behind a v-if that toggles often. What accumulates?",
    "choices": [
      "Nothing - Vue clears intervals when components unmount",
      "A new live interval per mount, each still firing against unmounted component state",
      "Memory only, but the intervals themselves stop",
      "Duplicate DOM nodes"
    ],
    "answer": 1,
    "why": [
      "Vue tears down its own subscriptions - browser timers are yours to clear.",
      null,
      "The intervals keep firing forever - that's the leak's active half, not just retained memory.",
      "The DOM is correctly removed; the timer outliving it is the problem."
    ],
    "explain": "Whatever mount starts, unmount must stop. Each v-if cycle mounts a fresh component that starts a fresh interval; without clearInterval in onUnmounted they all keep running."
  },
  {
    "q": "You need cartTotal to always equal the sum of cart item prices. Which tool?",
    "choices": [
      "A watcher on the cart that updates a cartTotal ref",
      "A computed that reduces over the cart",
      "onUpdated recalculating the total after each render",
      "watchEffect writing into a cartTotal ref"
    ],
    "answer": 1,
    "why": [
      "It works until the day an update path skips it - a maintained copy of derivable data is the anti-pattern this phase names.",
      null,
      "onUpdated fires after every render of anything - maximum wasted work, minimum precision.",
      "Same second-source-of-truth problem in auto-tracking clothes."
    ],
    "explain": "A value that follows from other values is a derivation: computed. Watchers are for actions - fetching, saving, timing - not for maintaining data that computed can express."
  }
]
```


---

# When Vue Breaks

Vue fails quieter than most frameworks. Where React throws red error walls, Vue often just...
stops updating, leaving you staring at a screen that disagrees with your data and a console that
says nothing. The good news: nearly every silent failure is the *same* failure - something got
disconnected from the reactivity system - and phase 3 gave you the mechanism to reason about it.
This phase is the field guide.

## The cheat-card

| Symptom | Almost always means | Fix |
|---|---|---|
| UI ignores updates; console shows data changing | A value got disconnected: destructured `reactive`, spread copy, or an overwritten ref | Access through the proxy/ref; `toRefs` for handing pieces around (phase 3) |
| A ref "doesn't update" in script, works in template | Missing `.value` in script code | Templates auto-unwrap; scripts never do |
| `[Vue warn]: Set operation on key "x" failed: target is readonly` / prop warning | Assigning to a prop | Emit the change to the parent (phase 4) |
| Rows show the wrong state after delete/reorder | `v-for` with index keys | Stable `:key="item.id"` (phase 2) |
| `Property "x" was accessed during render but is not defined` | Typo, or the variable isn't top-level in `<script setup>` | Declare/rename it; everything the template uses must be top-level |
| `v-if`/`v-for` on one element behaving strangely | `v-if` evaluates first, can't see the loop variable | Computed filter, or `<template v-for>` wrapping the `v-if` (phase 2) |
| Watcher never fires | Source is a plain value (`props.x`) or nested mutation without `deep` | Getter source; targeted getter or `deep: true` (phase 6) |
| Timer/listener keeps running after component is gone | Started in `onMounted`, no `onUnmounted` | Pair every start with a stop (phase 6) |

## The silent one, step by step

The signature Vue mystery deserves the full treatment, because no error will ever point at it:

```html
<script setup>
import { reactive } from 'vue';

const state = reactive({ filters: { category: 'all', inStock: false } });
let { filters } = state;          // step 1: looks harmless

function reset() {
  filters = { category: 'all', inStock: false };   // step 2: updates nothing, forever
}
</script>
```

Walk it with the phase 3 mechanism: `state` is a proxy; reads through it are tracked. Step 1
copies the *current* `filters` object into a local variable. That object is itself reactive
(nested objects get proxied too), so mutations like `filters.category = 'x'` would still work -
which makes the bug even sneakier, because the code *half works*. Step 2 is the killer: it
reassigns the **local variable** to a brand-new plain object. `state.filters` never changed; the
template (subscribed to `state.filters`) has no reason to update; your local `filters` now points
at an object nothing renders.

The debugging heuristic that finds this class in minutes instead of hours:

💡 **Key point:** when the console shows correct data but the screen disagrees, don't debug your
logic - **audit the path between the reactive source and the code that changed it.** Every
destructure, spread, function argument, and reassignment along that path is a suspect. The question
is always "did this write go *through* the proxy/ref, or past it?"

## The half-forgotten .value

```js
const items = ref([]);

async function load() {
  items = await fetchItems();          // ✗ replaced the ref itself (TypeError with const)
  items.value = await fetchItems();    // ✓ wrote through the container
}

if (items.length === 0) { ... }        // ✗ silently wrong: refs have no .length
if (items.value.length === 0) { ... }  // ✓
```

The assignment version at least crashes when the ref is `const`. The *read* version is nastier:
`items.length` is `undefined`, `undefined === 0` is `false`, and your "empty state" logic just
never runs - no error anywhere. In templates none of this exists (auto-unwrap); in script, `.value`
every time. If you use TypeScript, both mistakes become squiggles - a real argument for it beyond
fashion.

## The prop mutation warning

```text
[Vue warn]: Set operation on key "status" failed: target is readonly.
```

Vue catches direct prop assignment (`props.status = 'done'`) with that warning. The subtler
version it *can't* fully protect: props holding objects. `props.user.name = 'x'` mutates the
parent's object through the reference - it may even appear to work - but now a child is editing
state it doesn't own, invisibly to anyone reading the parent. Either way the answer is phase 4's
contract: children `emit`, parents change.

## Actually look at it: Vue DevTools

Silent failures need visibility, and the browser extension provides exactly the view you need:
component tree, each component's live refs/computeds/props, and a timeline of emitted events. The
two moves that resolve most mysteries:

- Select the component and **compare its state panel to the screen.** State right + screen wrong =
  rendering/keys problem. State wrong = the write never landed (disconnection, `.value`, wrong
  copy) - and now you know which half of the app to search.
- **Watch the events tab** while reproducing. An emit that never appears means the child never
  sent it; one that appears with nothing changing means the parent isn't listening (typo'd event
  name, kebab/camel mismatch).

## Recap

1. Vue's classic failure is silent: a write that went *past* the tracking instead of through it.
   Audit the path, not the logic.
2. `.value` in script, never in templates; misreads (`ref.length`) fail quieter than miswrites.
3. Props: direct assignment warns, nested mutation corrupts silently - emit instead.
4. Index keys, `v-if`+`v-for`, undead timers: phase 2 and 6 rules, now as symptoms.
5. Vue DevTools turns "the screen is wrong" into "this specific state is (or isn't) wrong" - use
   it before guessing.

```quiz
[
  {
    "q": "let { user } = reactive(store); user = await fetchUser(). The template keeps showing the old user with no warning. What happened?",
    "choices": [
      "fetchUser returned a non-reactive object, which templates can't display",
      "The reassignment changed only the local variable - store.user was never written, so subscribers were never notified",
      "await broke the reactivity chain",
      "The template needed a deep watcher on user"
    ],
    "answer": 1,
    "why": [
      "Templates display plain objects fine - the problem is nobody told the template anything changed.",
      null,
      "await is irrelevant - a synchronous reassignment fails identically.",
      "Watchers are for side effects; the template's own subscription was pointed at store.user all along - which didn't change."
    ],
    "explain": "Destructuring made a local pointer; reassigning it re-aims the pointer, not the store. Write through the source (store.user = ...) so the proxy can notify its subscribers."
  },
  {
    "q": "In script, if (results.length > 0) never runs even though the ref clearly holds items. Why?",
    "choices": [
      "Arrays inside refs need reactive() instead",
      "results is the ref container - it has no .length; the check reads undefined and is silently false. It needed results.value.length",
      "Script code runs before the items arrive",
      "length isn't reactive in Vue"
    ],
    "answer": 1,
    "why": [
      "ref holds arrays happily - accessed through .value.",
      null,
      "Timing could cause one false check, not a permanently dead branch.",
      "Through .value, length is fully tracked."
    ],
    "explain": "Auto-unwrap is a template luxury. In script the ref is a container object; forgetting .value on a read produces undefined and comparisons that quietly do the wrong thing."
  },
  {
    "q": "The console shows your data updating correctly but the screen never changes. Per this phase, what's the highest-value first step?",
    "choices": [
      "Add a key to force re-rendering",
      "Trace the path between the reactive source and the write - looking for destructures, spreads, and reassignments that bypassed tracking",
      "Rewrite the component with the Options API",
      "Wrap the data in an extra ref"
    ],
    "answer": 1,
    "why": [
      "A forced re-render would still read the unchanged source - repainting the same wrong answer.",
      null,
      "Both APIs sit on the same reactivity system and share the same disconnection bugs.",
      "Another layer of wrapping changes nothing about writes that go around the wrapper."
    ],
    "explain": "Data-right-screen-wrong means the write went past the tracking system. The bug lives on the path between source and write site - audit every hop for a copy or bypass."
  }
]
```


---

# Where to Go Next

Vue's ecosystem has a property worth appreciating before you dive in: the core pieces are
*official*. Router, state management, dev tools, and the meta-framework all ship from the Vue team
or its inner orbit, documented in one voice, designed to fit together. Less decision fatigue than
some neighborhoods of the frontend world - but the same rule applies: adopt each piece when its
pain arrives, not because a list said so.

## What you can already build

Phases 1-7 cover components, reactivity, forms, composition, and data fetching - a complete
single-view application skillset. Build two or three real things with exactly this before adding
tools: a expense tracker, a recipe box, a small dashboard against a public API. The pains you hit
(or don't) are the syllabus for everything below.

## The map: pain → tool

| When you feel this | Reach for | What it is |
|---|---|---|
| "I need URLs, pages, and back-button behavior" | **Vue Router** | The official client-side router: paths mapped to components, nested layouts, guards |
| "Distant components keep needing the same changing state" | **Pinia** | The official store: reactive state + actions in one place, DevTools-integrated |
| "I need SEO, fast first paint, or a server" | **Nuxt** | The Vue meta-framework: server rendering, file-based routing, data conventions |
| "My fetch logic repeats: loading, errors, caching, refetching" | **TanStack Query (Vue)** or Nuxt's data layer | Server-data management over hand-rolled fetch effects |
| "I want ready-made accessible components" | **PrimeVue, Vuetify, or Radix Vue** | Component libraries - pick by design taste, check accessibility claims |
| "I keep re-writing the same composable" | **VueUse** | A large collection of well-tested composables (debounce, storage, sensors) |

Two notes on the big ones:

**Pinia, sized fairly.** A Pinia store is close to something you already know: a composable with
hoisted state (phase 5's "refs at module scope" aside, formalized). If `provide`/`inject` plus a
composable is holding up fine, you're not behind - Pinia earns its place when shared state grows
update logic, needs DevTools history, or is touched from many corners.

**Nuxt, sized fairly.** Nuxt is to Vue what Next is to React: rendering on a server for SEO and
first paint, file-based routing, and a data-fetching layer - plus more convention (auto-imports,
directory structure) than base Vue. The reasoning in our
[Next.js guide's opening phase](../nextjs-from-zero/01-what-nextjs-actually-is.md) - what a SPA
costs you and when a server fixes it - transfers to Nuxt nearly word for word. Same decision, Vue
vocabulary.

## Options API code in the wild

You will inherit components written in the older dialect - `data()`, `methods:`, `computed:` as
object sections, `this.count` everywhere. Translation is mechanical once you know both sides:
`data` fields are refs, `methods` are functions, `computed:` entries are `computed()`, lifecycle
options (`mounted()`) are the `on*` hooks, and `this.x` becomes the direct reference. No rewrite
crusades required - the two dialects interoperate component-by-component, and understanding old
code is cheaper than churning it.

## Additional resources

- [vuejs.org](https://vuejs.org) - the official docs, among the best-written in frontend; the
  tutorial track mirrors this guide's arc with runnable examples.
- [pinia.vuejs.org](https://pinia.vuejs.org) - short, worth reading even before you need a store,
  for its state-design opinions.
- [VueUse](https://vueuse.org) - browse it once to calibrate what composables can be; read a few
  sources - they're compact masterclasses in phase 5's pattern.

## Recap

1. Router, Pinia, Nuxt: official, documented together, adopted separately - each on its own pain.
2. Pinia ≈ composables with formal shared state; skip until sharing hurts.
3. Nuxt is the Vue answer to the SPA costs our Next guide's phase 1 lays out - same trade, same
   reasoning.
4. Options API is legacy dialect, not legacy framework: read it fluently, migrate opportunistically.

```quiz
[
  {
    "q": "An app's theme, current user, and a 40-line cart with add/remove/discount logic are all shared via provide/inject. Which piece most clearly wants a Pinia store?",
    "choices": [
      "The theme - it's read everywhere",
      "The cart - shared state with real update logic and history worth inspecting",
      "The current user - it's the most sensitive data",
      "All three equally"
    ],
    "answer": 1,
    "why": [
      "Widely-read, rarely-changed, no logic: the provide/inject sweet spot - moving it buys nothing.",
      null,
      "Sensitivity is an auth concern, not a state-tool concern - a store adds no security.",
      "Theme and user are fine where they are; migrating them is churn without payoff."
    ],
    "explain": "Stores earn their keep where shared state meets update logic - actions, DevTools history, many writers. Simple broadcast context stays happily in provide/inject."
  },
  {
    "q": "When does moving from Vue+Vite to Nuxt become the right call?",
    "choices": [
      "Once an app exceeds roughly twenty components",
      "When you need what a server adds: SEO-ready HTML, fast first paint, server-side data access",
      "Nuxt is the current default; new Vue projects should start there",
      "When you want to use composables"
    ],
    "answer": 1,
    "why": [
      "Component count measures size, not architecture - a 200-component dashboard behind a login is still a fine SPA.",
      null,
      "For public content sites that's arguable - but as a blanket rule it ignores the SPA cases where a server buys nothing.",
      "Composables are core Vue - available everywhere, framework or not."
    ],
    "explain": "Nuxt is the server-in-front decision, Vue edition: adopt it for the SPA costs it eliminates (blank first paint, crawler-invisible content, exposed data layer), not as a default."
  }
]
```
