Files
policy-ui/.claude/skills/vue/references/reactivity.md
HaimKortovich a2eb1f3789 Add nuxt-skills and update auto quotes to use new policy API structure
- Add nuxt-skills (vue, nuxt, nuxt-ui) to .claude/skills/
- Create useCustomerSelection() composable for managing insured/buyer selection
- Create usePolicyApi() composable for policy API operations
- Update auto quote components to use insured/buyer instead of client
- Update vehicle fields: remove valorVehiculo, add market_value, requested_value, rc_limits
- Make chassis_number and engine_number optional
- Update auto quote types and composables to match new API structure
- Update auto quote page to submit to policy API with new structure
2026-04-27 14:56:53 -05:00

6.0 KiB

name, description
name description
Vue Reactivity System Core reactivity primitives - ref, reactive, computed, and watchers

Vue Reactivity System

Vue's reactivity system enables automatic tracking of state changes and DOM updates.

ref()

Create reactive primitive values with ref(). Access/modify via .value in JavaScript, auto-unwrapped in templates.

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0
count.value++
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

Typing refs

import { ref } from 'vue'
import type { Ref } from 'vue'

// Type inference
const year = ref(2020) // Ref<number>

// Explicit generic
const name = ref<string | null>(null)

// Ref type annotation
const id: Ref<string | number> = ref('abc')

reactive()

Create reactive objects. No .value needed, but cannot reassign the entire object.

import { reactive } from 'vue'

interface State {
  count: number
  name: string
}

const state: State = reactive({
  count: 0,
  name: 'Vue'
})

state.count++ // reactive

Limitations of reactive()

  1. Only works with objects - not primitives
  2. Cannot replace entire object - loses reactivity
  3. Destructuring loses reactivity - use toRefs() instead
const state = reactive({ count: 0 })

// ❌ Loses reactivity
let { count } = state

// ✅ Use toRefs
import { toRefs } from 'vue'
const { count } = toRefs(state)

Recommendation

Use ref() as the primary API for declaring reactive state - it works with any value type and has consistent behavior.

Deep Reactivity

Both ref() and reactive() are deeply reactive by default:

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

// These trigger updates
obj.value.nested.count++
obj.value.arr.push('baz')

Use shallowRef() or shallowReactive() to opt out of deep reactivity for performance.

DOM Update Timing

DOM updates are batched and asynchronous. Use nextTick() to wait for updates:

import { ref, nextTick } from 'vue'

const count = ref(0)

async function increment() {
  count.value++
  await nextTick()
  // DOM is now updated
}

Ref Unwrapping Rules

  • In templates: Top-level refs auto-unwrap
  • In reactive objects: Refs auto-unwrap when accessed as properties
  • In arrays/collections: Refs do NOT auto-unwrap
const count = ref(0)
const state = reactive({ count })

console.log(state.count) // 0 (unwrapped)

const books = reactive([ref('Vue Guide')])
console.log(books[0].value) // Need .value

computed()

Derive values from reactive state with automatic caching. Only re-evaluates when dependencies change.

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// Readonly computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`)

// Writable computed
const fullNameWritable = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue: string) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

Computed Best Practices

  • Getters should be pure - no side effects, no mutating other state
  • Don't mutate computed values - mutate the source instead
  • Use computed over methods for derived data (caching benefit)
// ✅ Cached - only recalculates when items changes
const activeItems = computed(() => items.value.filter(x => x.active))

// ❌ Not cached - runs on every render
function getActiveItems() {
  return items.value.filter(x => x.active)
}

watch()

Explicitly watch reactive sources and run side effects when they change. Lazy by default.

import { ref, watch } from 'vue'

const id = ref(1)

watch(id, async (newId, oldId) => {
  const data = await fetchData(newId)
  // handle data...
})

Watch Source Types

const x = ref(0)
const obj = reactive({ count: 0 })

// Single ref
watch(x, (newX) => console.log(newX))

// Getter function
watch(() => obj.count, (count) => console.log(count))

// Multiple sources
watch([x, () => obj.count], ([newX, newCount]) => {
  console.log(newX, newCount)
})

Watch Options

watch(source, callback, {
  immediate: true,  // Run immediately on creation
  deep: true,       // Watch nested properties
  once: true,       // Trigger only once (3.4+)
  flush: 'post'     // Run after DOM update
})

watchEffect()

Automatically tracks dependencies and runs immediately. Re-runs when any tracked dependency changes.

import { ref, watchEffect } from 'vue'

const todoId = ref(1)
const data = ref(null)

watchEffect(async () => {
  const response = await fetch(`/api/todos/${todoId.value}`)
  data.value = await response.json()
})

watch vs watchEffect

Feature watch watchEffect
Dependency tracking Explicit Automatic
Lazy Yes No (immediate)
Access old value Yes No
Best for Specific sources Multiple dependencies

Watcher Cleanup (3.5+)

Cancel stale async operations:

import { watch, onWatcherCleanup } from 'vue'

watch(id, async (newId) => {
  const controller = new AbortController()

  fetch(`/api/${newId}`, { signal: controller.signal })

  onWatcherCleanup(() => controller.abort())
})

Stopping Watchers

const stop = watch(source, callback)
const stop2 = watchEffect(() => { /* ... */ })

// Stop manually
stop()
stop2()

// Pause/Resume (3.5+)
const { stop, pause, resume } = watchEffect(() => { /* ... */ })