vuejs 518 Q&As

Vue.js FAQ & Answers

518 expert Vue.js answers researched from official documentation. Every answer cites authoritative sources you can verify.

Composition API

35 questions
A

The correct syntax for onBeforeUnmount in Vue 3 Composition API:

import { onBeforeUnmount } from 'vue'

onBeforeUnmount(() => {
  // cleanup logic here
})

Type signature:

function onBeforeUnmount(callback: () => void): void

Key characteristics:

  • Called right before a component instance is unmounted
  • The component instance is still fully functional when this hook runs
  • Not called during server-side rendering
  • Used for cleanup operations that need to happen before unmounting

Usage example:

<script setup>
import { onBeforeUnmount } from 'vue'

onBeforeUnmount(() => {
  console.log('Component is about to unmount')
  // Clean up resources, cancel timers, etc.
})
</script>

Sources:

95% confidence
A

The correct syntax for onUpdated in Vue 3 Composition API:

import { onUpdated } from 'vue'

onUpdated(() => {
  // callback executed after component updates its DOM
})

Full example:

<script setup>
import { ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() => {
  // text content should be the same as current `count.value`
  console.log(document.getElementById('count').textContent)
})
</script>

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

Type signature:

function onUpdated(callback: () => void): void

Important:

  • Must be called synchronously during component setup
  • Called after any DOM update caused by reactive state changes
  • Parent component's onUpdated runs after child components
  • Do not modify state inside onUpdated or you'll create an infinite loop

Sources:

95% confidence
A

watch requires you to explicitly specify what to track and only runs when that source changes. watchEffect automatically tracks all reactive dependencies accessed during execution and runs immediately on creation.

Key Differences:

1. Dependency Declaration:

  • watch: Explicit - you must specify what to watch
  • watchEffect: Implicit - automatically tracks reactive properties accessed during execution

2. Execution Timing:

  • watch: Lazy by default - only runs when the watched source changes (unless { immediate: true })
  • watchEffect: Eager - runs immediately on creation and re-runs when dependencies change

3. Access to Previous Values:

  • watch: Yes - callback receives both new and old values
  • watchEffect: No - only has access to current values

4. Control:

  • watch: More precise control over when the callback fires
  • watchEffect: Less control - fires whenever any tracked dependency changes

Code Example:

// watch - explicit source, lazy execution
watch(todoId, async (newId, oldId) => {
  const response = await fetch(`https://api.example.com/todos/${newId}`)
  data.value = await response.json()
})

// watchEffect - automatic tracking, immediate execution
watchEffect(async () => {
  const response = await fetch(`https://api.example.com/todos/${todoId.value}`)
  data.value = await response.json()
})

When to use which:

  • Use watch when you need the previous value or want lazy execution
  • Use watchEffect for convenience when tracking multiple dependencies automatically

Sources:

95% confidence
A

<script setup> is compile-time syntactic sugar for using Composition API in Single File Components and is the recommended syntax for Vue 3 SFCs.

Key Differences

1. Syntax & Boilerplate

  • <script setup>: Top-level bindings (imports, variables, functions) are automatically exposed to the template. No return statement needed.
  • setup(): Must explicitly return an object containing all properties/methods you want to expose to the template.
<!-- script setup -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() { count.value++ }
</script>

<!-- regular setup() -->
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    function increment() { count.value++ }
    return { count, increment } // Must explicitly return
  }
}
</script>

2. Performance

  • <script setup>: Template is compiled into a render function in the same scope, without an intermediate proxy. Better runtime performance.
  • setup(): Uses an intermediate proxy for returned properties.

3. TypeScript Support

  • <script setup>: Can declare props and emits using pure TypeScript. Better IDE type-inference performance (less work for the language server).
  • setup(): Requires more verbose type annotations.

4. Execution Timing

  • <script setup>: Code executes every time a component instance is created (compiled as the content of the setup() function).
  • Regular <script>: Executes once when the module is first imported.

5. Props & Emits Declaration

  • <script setup>: Use compiler macros defineProps() and defineEmits() (hoisted to module scope).
  • setup(): Receives props as first argument, context (with emit) as second argument.

6. Limitations

  • <script setup>: Cannot use src attribute, doesn't support in-DOM root component templates.
  • setup(): Must return synchronously (except when component is descendant of Suspense).

When to Use Each

  • <script setup>: Recommended for all new SFC code using Composition API.
  • setup(): Only when integrating Composition API into existing Options API codebases or when not using build tools.

Sources:

95% confidence
A

In Vue 3 <script setup>, use the defineEmits() compiler macro to declare component events.

Basic syntax (array declaration):

<script setup>
const emit = defineEmits(['update', 'delete'])

emit('update', payload)
</script>

Object syntax with validation:

<script setup>
const emit = defineEmits({
  update: (id) => {
    return typeof id === 'number'
  }
})
</script>

TypeScript type-based declaration:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'update', id: number): void
  (e: 'delete', id: number): void
}>()
</script>

TypeScript alternative syntax (Vue 3.3+):

<script setup lang="ts">
const emit = defineEmits<{
  update: [id: number]
  delete: [id: number]
}>()
</script>

Key points:

  • defineEmits() is a compiler macro—no import needed
  • Returns an emit function equivalent to $emit in templates
  • Accepts the same values as the Options API emits option
  • Cannot mix runtime and type-based declarations
  • Options are hoisted and cannot reference local variables

Sources:

95% confidence
A

In Vue 3 Composition API, define a computed property with both getter and setter by passing an object with get() and set() methods to the computed() function:

import { ref, computed } from 'vue'

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

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

When you assign a value to fullName.value = 'Jane Smith', the setter is invoked and updates the underlying refs accordingly.

Key points:

  • Import computed from 'vue'
  • Pass an object (not a function) to computed()
  • Define get() method for reading the value
  • Define set(newValue) method for writing the value
  • Access with .value property like any ref

Sources:

95% confidence
A

To watch multiple sources in Vue 3 Composition API, pass an array of sources as the first argument to watch(). The callback receives arrays of new and old values corresponding to each source.

Syntax:

watch([source1, source2, ...], ([newVal1, newVal2, ...], [oldVal1, oldVal2, ...]) => {
  // callback logic
})

Complete Example:

import { ref, watch } from 'vue'

const x = ref(0)
const y = ref(0)

watch([x, () => y.value], ([newX, newY], [prevX, prevY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
  console.log(`previous: x was ${prevX}, y was ${prevY}`)
})

Key Points:

  • The first argument is an array of watch sources (refs, reactive objects, or getter functions)
  • The callback's first parameter is an array of new values
  • The callback's second parameter is an array of old values
  • Values correspond to sources by index position
  • The watcher triggers when any source changes

Type Signature:

function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): WatchHandle

Sources:

95% confidence
A

defineExpose explicitly exposes properties and methods from a <script setup> component to parent components via template refs.

Components using <script setup> are closed by default — the public instance retrieved via template refs does not expose any bindings declared inside <script setup>. To make specific properties or methods accessible to parent components, use defineExpose:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

When a parent gets a template ref to this component, the retrieved instance will be { a: number, b: number } (refs are automatically unwrapped).

Key Points:

  • defineExpose is a compiler macro (no import needed)
  • Without it, <script setup> components expose nothing to parent components
  • Must be called before any await expressions
  • Only properties passed to defineExpose will be accessible from outside the component

Sources:

95% confidence
A

No, you do not need to use .value to access a ref in Vue 3 templates.

Refs are automatically unwrapped when used inside templates, so you can reference them directly:

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

<template>
  <!-- No .value needed in template -->
  <div>{{ count }}</div>
  <button @click="count++">Increment</button>
</template>

Important caveat: Auto-unwrapping only applies to top-level refs in the template render context. If a ref is nested inside an object or reactive array, you would need .value:

<template>
  <!-- Auto-unwrapped (top-level) -->
  <div>{{ count }}</div>
  
  <!-- NOT auto-unwrapped (nested in object) -->
  <div>{{ object.count.value }}</div>
</template>

In JavaScript code (inside <script>), you always need .value to access or modify the ref.

Sources:

95% confidence
A

To watch a nested property in a reactive object in Vue 3, use one of these three methods:

Method 1: Getter Function (Recommended for specific nested properties)

import { watch } from 'vue'

const state = reactive({ user: { name: 'John' } })

watch(
  () => state.user.name,
  (newName, oldName) => {
    console.log(`Name changed from ${oldName} to ${newName}`)
  }
)

Method 2: Deep Watcher (For all nested properties)

import { watch, reactive } from 'vue'

const state = reactive({ user: { name: 'John', age: 30 } })

watch(
  () => state.user,
  (newValue, oldValue) => {
    // fires on any nested property mutation
    // Note: newValue === oldValue (same object reference)
  },
  { deep: true }
)

Method 3: Options API Path Notation

export default {
  data() {
    return {
      user: { profile: { name: 'John' } }
    }
  },
  watch: {
    'user.profile.name'(newValue, oldValue) {
      console.log('Name changed')
    }
  }
}

Key Points:

  • Use getter syntax () => obj.nested.prop for specific nested properties
  • Add { deep: true } to watch all nested changes (can be expensive on large objects)
  • Options API supports dot-delimited paths like 'some.nested.key'
  • In Vue 3.5+, deep can be a number indicating max traversal depth

Sources:

95% confidence
A

The deep option in Vue 3's watch forces the watcher to traverse all nested properties in an object, triggering the callback when any nested property changes (not just when the object reference changes).

Default behavior without deep:

// Only triggers when state.someObject is reassigned
watch(() => state.someObject, (newValue, oldValue) => {
  // Won't fire if you do: state.someObject.nestedProp = 'new value'
})

With deep: true:

// Options API
watch: {
  someObject: {
    handler(newValue, oldValue) {
      // Fires on nested mutations like someObject.nested.prop = 'changed'
    },
    deep: true
  }
}

// Composition API
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // Fires on any nested property change
  },
  { deep: true }
)

Vue 3.5+ numeric depth:

watch(source, callback, { deep: 3 }) // Traverses max 3 levels deep

When to use it:

  • When you need to react to mutations inside nested objects/arrays
  • When the data structure's shape matters, not just the reference

When NOT to use it:

  • On large data structures (performance cost)
  • When you can watch specific nested properties with a getter instead:
    watch(() => state.someObject.specific.property, callback) // More efficient
    

Important: When watching a reactive object directly (not via getter), deep watching is implicit:

const obj = reactive({ nested: { count: 0 } })
watch(obj, callback) // Already deep, no need for { deep: true }

Sources:

95% confidence
A

watchEffect returns a WatchHandle object that is callable as a function and provides methods to control the watcher's lifecycle.

Return Type:

interface WatchHandle {
  (): void // callable, same as `stop`
  pause: () => void
  resume: () => void
  stop: () => void
}

How to use it:

  1. Stop the watcher - Call the returned handle as a function or use .stop():
const stopHandle = watchEffect(() => {
  console.log(count.value)
})

// Later, permanently stop the watcher
stopHandle()
// or
stopHandle.stop()
  1. Pause and resume - Temporarily pause watcher execution:
const handle = watchEffect(() => {
  console.log(count.value)
})

handle.pause()  // Temporarily stop watching
handle.resume() // Resume watching
  1. Automatic cleanup - In components, watchers are automatically stopped when the component unmounts, but manual cleanup is useful for watchers created in composables or outside component lifecycle.

Sources:

95% confidence
A

onRenderTracked is a development-mode-only debug hook that is called when a reactive dependency is tracked by the component's render effect.

Purpose: It helps developers debug and understand which reactive properties are being accessed during component rendering.

When it fires: Every time the component's render function reads a reactive property, onRenderTracked is triggered with details about that dependency.

Type signature:

function onRenderTracked(callback: DebuggerHook): void

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes /* 'get' | 'has' | 'iterate' */
  key: any
}

Common usage pattern:

import { onRenderTracked } from 'vue'

onRenderTracked((event) => {
  debugger // Inspect which dependency was tracked
  console.log(event.target, event.key, event.type)
})

Key characteristics:

  • Only runs in development mode (not production)
  • Not called during server-side rendering
  • Fires during initial render and whenever new dependencies are tracked
  • Used alongside onRenderTriggered for comprehensive reactivity debugging

Sources:

95% confidence
A

In Vue 3 Composition API with <script setup>, you access the value of a ref using the .value property.

import { ref } from 'vue'

const count = ref(0)

// Access the value
console.log(count.value) // 0

// Modify the value
count.value++
console.log(count.value) // 1

The .value property is required in JavaScript/TypeScript code to access or mutate the ref's value. This allows Vue's reactivity system to track when the ref is accessed or changed.

Note: In templates, refs are automatically unwrapped, so you use {{ count }} instead of {{ count.value }}.

Sources:

95% confidence
A

The onDeactivated lifecycle hook is called when a component wrapped in <KeepAlive> is removed from the DOM and placed into the cache. It's also called when the component is unmounted.

Primary Use Cases:

  • Pause or stop ongoing operations (timers, animations, video playback)
  • Suspend resource-intensive operations when component becomes inactive
  • Clean up temporary resources while preserving component state

Example:

<script setup>
import { onActivated, onDeactivated } from 'vue'

onDeactivated(() => {
  // Called when removed from DOM into cache
  // Pause timers, stop animations, suspend subscriptions
})

onActivated(() => {
  // Called when re-inserted from cache
  // Resume operations
})
</script>

Key Distinction: Unlike standard unmount hooks, onDeactivated fires when cached components are hidden (not destroyed), allowing you to pause rather than fully tear down component functionality.

Note: Not called during server-side rendering.

Sources:

95% confidence
A

The onRenderTriggered lifecycle hook is a development-mode debugging tool that is called when a reactive dependency triggers the component's render effect to re-run.

Purpose

It allows you to debug and track which specific reactive state changes are causing your component to re-render. When the hook fires, it provides a debugger event containing detailed information about what triggered the update.

The Hook Receives

A DebuggerEvent object containing:

  • effect: The reactive effect that was triggered
  • target: The reactive object that was modified
  • type: The operation type ('set', 'add', 'delete', or 'clear')
  • key: The property key that was modified
  • newValue: The new value (if applicable)
  • oldValue: The previous value (if applicable)
  • oldTarget: The previous Map/Set (if applicable)

Example Usage

import { onRenderTriggered } from 'vue'

onRenderTriggered((event) => {
  console.log('Render triggered by:', event.type, event.key)
  console.log('Target:', event.target)
  console.log('New value:', event.newValue)
  debugger
})

Important Notes

  • Development-only: This hook is not called in production builds
  • Not called during SSR: Only works in client-side rendering
  • Used primarily for performance debugging and understanding reactivity flow

Sources:

95% confidence
A

Yes, you can use multiple onMounted hooks in the same component in Vue 3. All registered callbacks will be executed in the order they were registered when the component mounts.

This is a key feature of Vue 3's Composition API that enables better code organization, especially when using composables:

<script setup>
import { onMounted } from 'vue'

// First onMounted hook
onMounted(() => {
  console.log('First mounted callback')
})

// Second onMounted hook
onMounted(() => {
  console.log('Second mounted callback')
})

// Using a composable that also calls onMounted
useMouse() // This composable may have its own onMounted hook
</script>

Key requirements:

  • All onMounted calls must be registered synchronously during component setup
  • onMounted() can be called in external functions (like composables) as long as the call stack is synchronous and originates from within setup()
  • Each call registers an additional callback rather than replacing the previous one

This pattern is particularly useful when using multiple composables that each need to perform setup work when the component mounts.

Sources:

95% confidence
A

In <script setup>, access slots using the useSlots() helper function:

<script setup>
import { useSlots } from 'vue'

const slots = useSlots()

// Access specific slots
const hasDefaultSlot = !!slots.default
const hasHeaderSlot = !!slots.header
</script>

useSlots() returns an object containing all slot functions, equivalent to setupContext.slots in the regular Composition API setup() function.

Note: In templates, you can access slots directly as $slots without importing anything. The useSlots() function is primarily for programmatic slot access within <script setup> logic, though this use case is relatively rare.

For TypeScript users in Vue 3.3+, use defineSlots() for type-safe slot declarations instead.

Sources:

95% confidence
A

Use the useAttrs() helper function imported from 'vue':

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

The attrs object contains all fallthrough attributes (attributes passed to the component that are not declared as props or emits). Note that:

  • The attrs object always reflects the latest fallthrough attributes but is not reactive (for performance reasons)
  • You cannot watch it for changes - use a prop if you need reactivity, or use onUpdated() to perform side effects with the latest attrs
  • In templates, you can access attrs directly as $attrs without needing useAttrs()

Sources:

95% confidence
A

Both watch() and watchEffect() return a stop function that you call to stop the watcher.

const stop = watchEffect(() => {
  // watcher logic
})

// later, when no longer needed
stop()

This works identically for watch():

const stop = watch(source, (newValue, oldValue) => {
  // watcher logic
})

// stop the watcher
stop()

Important: Watchers created synchronously inside setup() or <script setup> are automatically stopped when the component unmounts. Manual stopping is only necessary when:

  • Creating watchers asynchronously (e.g., in a callback or setTimeout)
  • You need to stop the watcher before component unmount

Vue 3.5+ Enhanced API:

const { stop, pause, resume } = watchEffect(() => {})

pause()  // temporarily pause
resume() // resume watching
stop()   // stop permanently

Sources:

95% confidence
A

The onActivated lifecycle hook is used for components cached by <KeepAlive>. It registers a callback that executes when the component instance is inserted into the DOM from the cache.

When it fires:

  • On initial mount of a KeepAlive-wrapped component
  • Every time the component is re-inserted from the cache (becomes active again)

Signature:

function onActivated(callback: () => void): void

Example usage:

<script setup>
import { onActivated } from 'vue'

onActivated(() => {
  // called on initial mount
  // and every time it is re-inserted from the cache
  console.log('Component is now active')
})
</script>

Key points:

  • Works with both the root component and descendant components in the cached tree
  • Not called during server-side rendering
  • Commonly paired with onDeactivated for cleanup when component is cached

Sources:

95% confidence
A
import { onBeforeUpdate } from 'vue'

onBeforeUpdate(() => {
  // callback executed before component updates its DOM
})

Syntax:

  • Import onBeforeUpdate from 'vue'
  • Call it with a callback function that takes no arguments
  • The callback executes right before the component updates its DOM tree due to a reactive state change
  • Safe to modify component state inside this hook
  • Not called during server-side rendering

Type Signature:

function onBeforeUpdate(callback: () => void): void

Example in <script setup>:

<script setup>
import { onBeforeUpdate } from 'vue'

onBeforeUpdate(() => {
  // Access DOM state before Vue updates it
  console.log('Component is about to update')
})
</script>

Sources:

95% confidence
A

The correct syntax for watching a single ref in Vue 3 Composition API:

import { ref, watch } from 'vue'

const myRef = ref('')

watch(myRef, (newValue, oldValue) => {
  // callback runs when myRef changes
})

Key points:

  • Pass the ref directly as the first argument (not myRef.value)
  • The callback receives newValue and oldValue as parameters
  • The callback triggers automatically when the ref's value changes
  • Returns a stop function to stop watching: const stop = watch(...)

Optional third parameter for configuration:

watch(myRef, (newValue, oldValue) => {
  // callback
}, {
  immediate: true,  // run immediately on creation
  deep: true,       // deep watch for objects
  flush: 'post'     // timing: 'pre' | 'post' | 'sync'
})

Sources:

95% confidence
A

The flush option in Vue 3 watch() controls the timing of when the watcher callback executes relative to Vue's component rendering cycle.

Possible Values:

  1. 'pre' (default) - The watcher runs just prior to component rendering
  2. 'post' - Defers the watcher until after component rendering (use this when you need to access updated DOM)
  3. 'sync' - Triggers the watcher immediately when a reactive dependency changes (use with caution due to performance and data consistency concerns)

Example:

watch(source, callback, {
  flush: 'post'
})

Convenience Aliases:

Vue provides convenience functions that set the flush option:

  • watchPostEffect() - equivalent to flush: 'post'
  • watchSyncEffect() - equivalent to flush: 'sync'

Sources:

95% confidence
A

In Vue 3 Composition API, use the computed() function imported from 'vue':

import { computed } from 'vue'

// Read-only computed property
const doubleCount = computed(() => count.value * 2)

The computed() function accepts a getter function and returns a computed ref. Access the value using .value in JavaScript (auto-unwrapped in templates).

Writable computed (when you need both getter and setter):

const fullName = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

Key rules:

  • Computed properties are cached based on reactive dependencies
  • Getter functions must be pure (no side effects, mutations, or async operations)
  • Treat computed values as read-only; modify source state to trigger updates

Sources:

95% confidence
A

The correct syntax for defining a reactive object in Vue 3 Composition API is:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

Key points:

  1. Import the reactive function from 'vue'
  2. Pass an object to reactive() - it only accepts objects, not primitives
  3. Deep reactivity - all nested properties are automatically reactive
  4. Returns a Proxy - not the original object, so always use the returned proxy

Complete component example:

<script setup>
import { reactive } from 'vue'

const state = reactive({ 
  count: 0,
  user: {
    name: 'John'
  }
})

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

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

Type signature:

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

Important: Use the reactive proxy exclusively, not the original object. The returned proxy uses ES Proxy and makes all nested properties reactive automatically.

Sources:

95% confidence
A

The correct syntax for defining a ref in Vue 3 Composition API:

import { ref } from 'vue'

const count = ref(0)

Key points:

  1. Import: import { ref } from 'vue'
  2. Create: const variableName = ref(initialValue)
  3. Access in JavaScript: Use .value property → count.value
  4. Access in template: Refs are automatically unwrapped → {{ count }} (no .value needed)

Type signature:

function ref<T>(value: T): Ref<UnwrapRef<T>>

Complete example:

<script setup>
import { ref } from 'vue'

const count = ref(0)

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

<template>
  <button @click="increment">
    {{ count }}  <!-- No .value needed in template -->
  </button>
</template>

Important characteristics:

  • The ref object is mutable and reactive
  • Mutating .value triggers reactive updates
  • Objects assigned to refs are deeply reactive (use shallowRef() to avoid)
  • Nested refs are automatically unwrapped

Sources:

95% confidence
A

The correct syntax for onMounted in Vue 3 Composition API:

import { onMounted } from 'vue'

onMounted(() => {
  // callback code here
})

Key Points:

  • Import onMounted from 'vue'
  • Call it with a callback function that executes after the component is mounted
  • Must be called synchronously during component setup (typically in <script setup> or inside setup())
  • The callback receives no arguments
  • Hook is called after all synchronous child components are mounted and the component's DOM tree is inserted into the parent container
  • Not called during server-side rendering

Example:

<script setup>
import { ref, onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // Access DOM elements
  console.log('Component is mounted!')
})
</script>

<template>
  <div ref="el"></div>
</template>

Sources:

95% confidence
A

Use withDefaults() to wrap defineProps() with a type parameter, passing an object with default values as the second argument:

interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

Key rules:

  • First argument: defineProps<YourInterface>()
  • Second argument: object literal with default values
  • Arrays and objects must be wrapped in functions to avoid shared references
  • Properties with defaults have their optional flags removed in the resulting type

Complete example:

<script setup lang="ts">
interface Props {
  title?: string
  count?: number
  tags?: string[]
  config?: { theme: string }
}

const props = withDefaults(defineProps<Props>(), {
  title: 'Default Title',
  count: 0,
  tags: () => [],
  config: () => ({ theme: 'light' })
})
</script>

Sources:

95% confidence
A

In Vue 3 Composition API, create a template ref by declaring a ref with ref(null) and matching its variable name to the template ref attribute value:

<script setup>
import { ref, onMounted } from 'vue'

const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

Vue 3.5+ introduced useTemplateRef() which decouples the ref name from the variable name:

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const input = useTemplateRef('my-input')

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>

Key requirements:

  • Template refs are null until the component mounts
  • Access them in onMounted() or later lifecycle hooks
  • With v-for, the ref value becomes an array of elements

Sources:

95% confidence
A

In Vue 3's <script setup>, use defineProps() to define props. It's a compiler macro that doesn't need to be imported.

Two ways to define props:

  1. Runtime declaration (using object syntax):
<script setup>
const props = defineProps({
  title: String,
  count: Number,
  isActive: Boolean
})
</script>
  1. Type-based declaration (TypeScript):
<script setup lang="ts">
const props = defineProps<{
  title: string
  count?: number
  isActive: boolean
}>()
</script>

Important: You cannot use both runtime and type-based declarations together - choose one approach.

Default values with type-based props:

For Vue 3.5+:

<script setup lang="ts">
const { title = 'Default Title', count = 0 } = defineProps<{
  title?: string
  count?: number
}>()
</script>

For Vue 3.4 and below, use withDefaults:

<script setup lang="ts">
const props = withDefaults(defineProps<{
  title?: string
  count?: number
}>(), {
  title: 'Default Title',
  count: 0
})
</script>

Sources:

95% confidence
A

The correct syntax for onUnmounted in Vue 3 Composition API:

import { onUnmounted } from 'vue'

onUnmounted(() => {
  // cleanup code here
})

Type signature:

function onUnmounted(callback: () => void): void

Key points:

  • Import from 'vue'
  • Takes a callback function with no parameters
  • Callback executes after component unmount and all child components are unmounted
  • Commonly used for cleanup: clearing timers, removing event listeners, closing connections

Example:

import { onMounted, onUnmounted } from 'vue'

let intervalId

onMounted(() => {
  intervalId = setInterval(() => {
    // do something
  }, 1000)
})

onUnmounted(() => {
  clearInterval(intervalId)
})

Note: This hook is not called during server-side rendering.

Sources:

95% confidence
A

The correct syntax for onBeforeMount in Vue 3 Composition API is:

import { onBeforeMount } from 'vue'

onBeforeMount(() => {
  // your code here
})

Function signature:

function onBeforeMount(callback: () => void): void

Usage in <script setup>:

<script setup>
import { onBeforeMount } from 'vue'

onBeforeMount(() => {
  console.log('Component is about to mount')
  // Perform initialization before DOM is created
})
</script>

Usage in setup() function:

import { onBeforeMount } from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('Component is about to mount')
    })
  }
}

Key characteristics:

  • Must be imported from 'vue'
  • Takes a callback function that executes before the component mounts
  • Called after reactive state setup but before DOM creation
  • Not called during server-side rendering
  • Must be called synchronously during setup()

Sources:

95% confidence
A

The immediate option in Vue 3 watch triggers the callback immediately on watcher creation. Its default value is false.

When set to true, the watcher callback executes immediately when the watcher is created, before any changes occur to the watched source. On this first immediate call, the oldValue parameter will be undefined since there is no previous value.

Example:

watch(source, (newValue, oldValue) => {
  // This runs immediately when immediate: true
}, { immediate: true })

Sources:

95% confidence
A

The onErrorCaptured lifecycle hook captures errors propagating from descendant components. It allows parent components to intercept and handle errors from their child component tree.

Function Signature:

function onErrorCaptured(callback: ErrorCapturedHook): void

type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) => boolean | void

Parameters:

  • err: The error object that was captured
  • instance: The component instance that triggered the error
  • info: A string specifying the error source type (e.g., "render", "setup", "watcher")

Captures errors from:

  • Component renders
  • Event handlers
  • Lifecycle hooks
  • setup() functions
  • Watchers
  • Custom directive hooks
  • Transition hooks

Error Propagation:

  • Errors bubble up from child to parent (bottom-to-top), similar to DOM events
  • Multiple onErrorCaptured hooks in the parent chain all execute
  • Return false to stop propagation and prevent further error handlers from being invoked
  • By default, all errors still reach app.config.errorHandler unless propagation is stopped

Common Pattern:

onErrorCaptured((err, instance, info) => {
  // Display error state to user
  errorMessage.value = err.message;
  // Return false to stop propagation
  return false;
});

Warning: Don't render the original content that caused the error in your error state, or you'll create an infinite render loop.

Production Note: In production builds, the info parameter becomes a shortened error code. See the Production Error Code Reference for mappings.

Sources:

95% confidence

Vue Router 4

34 questions
A

Vue Router 4 provides three methods to pass props to route components using the props option in route configuration:

1. Boolean Mode

Set props: true to automatically pass route.params as component props:

const routes = [
  { 
    path: '/user/:id', 
    component: User, 
    props: true 
  }
]

The component receives id as a prop instead of accessing $route.params.id.

2. Object Mode

Set props to an object for static props:

const routes = [
  { 
    path: '/promotion', 
    component: Promotion, 
    props: { newsletterPopup: false } 
  }
]

3. Function Mode

Use a function to transform route data into props:

const routes = [
  { 
    path: '/search', 
    component: SearchUser, 
    props: route => ({ query: route.query.q })
  }
]

URL /search?q=vue passes { query: 'vue' } as props to the component.

Named Views

For routes with named views, define props for each named view:

const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }
]

Best Practice

Keep the props function stateless—it only evaluates on route changes. Use a wrapper component if you need state-based props.

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's the precise answer:

Route params are dynamic segments in the URL path, defined with a colon prefix (:) in the route definition. They are part of the path structure itself.

Route query are parameters in the query string after the ? in the URL. They are appended to any path.

Key Differences:

  1. URL Structure:

    • Params: /user/eduardo (where eduardo is the param)
    • Query: /user?name=eduardo (where name=eduardo is the query)
  2. Route Definition:

    • Params require path definition: { path: '/user/:id' }
    • Query requires no route definition (works with any route)
  3. Programmatic Navigation:

    • Params: Must use route name when navigating
    router.push({ name: 'user', params: { id: 'eduardo' } })
    
    • Query: Can use path or name
    router.push({ path: '/user', query: { plan: 'private' } })
    
  4. Critical Difference:

    • params are ignored if a path is provided in navigation
    • query works with both path and name
  5. Access in Component:

    • Params: route.params.id
    • Query: route.query.plan

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's how to define dynamic route parameters:

Define dynamic route parameters using a colon (:) prefix in the path:

const routes = [
  // dynamic segment starts with a colon
  { path: '/users/:id', component: User }
]

Multiple parameters:

const routes = [
  // multiple params
  { path: '/users/:username/posts/:postId', component: UserPost }
]

Accessing parameters:

In the component, access parameters via $route.params:

<template>
  <div>User {{ $route.params.id }}</div>
</template>

With Composition API:

import { useRoute } from 'vue-router'

const route = useRoute()
console.log(route.params.id)

Matching behavior:

  • /users/:id matches /users/johnny, /users/123, etc.
  • :id becomes available as route.params.id
  • /users/:username/posts/:postId matches /users/eduardo/posts/123
    • route.params.username = "eduardo"
    • route.params.postId = "123"

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, nested routes are defined using the children option in your route configuration.

Basic syntax:

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // UserProfile will be rendered inside User's <router-view>
        // when /user/:id/profile is matched
        path: 'profile',
        component: UserProfile,
      },
      {
        // UserPosts will be rendered inside User's <router-view>
        // when /user/:id/posts is matched
        path: 'posts',
        component: UserPosts,
      }
    ]
  }
]

Key requirements:

  1. Parent component must include <router-view> - The nested routes render inside the parent component's <router-view> element

  2. Child paths are relative - Paths in children are relative to the parent path (no leading slash needed)

  3. Empty nested path for defaults - Use path: '' in a child route to render a default component when only the parent path is matched

  4. Infinite nesting - Each child route can have its own children array for deeper nesting

Example with default child:

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // renders when /user/:id is matched
      { path: '', component: UserHome },
      // renders when /user/:id/profile is matched  
      { path: 'profile', component: UserProfile },
    ]
  }
]

Sources:

95% confidence
A

The beforeEnter guard is a per-route navigation guard that you define directly on route configuration objects in Vue Router 4.

Definition:
beforeEnter is a property you add to individual route records that executes before entering that specific route. It only triggers when entering the route—not when params, query, or hash change within the same route.

Function Signature:

beforeEnter: (to, from) => {
  // navigation logic
}

Or as an array of functions:

beforeEnter: [guard1, guard2, guard3]

Parameters:

  • to: The target RouteLocationNormalized being navigated to
  • from: The current RouteLocationNormalized being navigated away from

Return Values:

  • false: Cancel the navigation
  • undefined or true: Validate the navigation
  • A route location (string or object): Redirect to a different location

Usage Example:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

Key Behaviors:

  • Only called when entering the route, not on param/query/hash changes
  • Not triggered if the route has a redirect property
  • Can be defined as a single function or array of functions
  • Executed after global beforeEach guards and before component guards

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, here's how to navigate back in history:

Primary Method

Use router.back() to navigate back by one entry in the browser history:

router.back()

This is equivalent to router.go(-1) and calls history.back() under the hood.

Alternative Method

Use router.go(n) where n is a negative integer:

router.go(-1)  // go back one record (same as router.back())
router.go(-2)  // go back two records
router.go(-3)  // go back three records

The router.go() method accepts an integer parameter indicating how many steps to move in the history stack (positive for forward, negative for backward). It behaves like window.history.go(n).

Note: If there aren't enough records in the history stack, these methods fail silently.

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's the verified answer:

router.afterEach() is a global navigation guard that runs after navigation has been confirmed. Unlike beforeEach guards, afterEach hooks:

  • Do not affect navigation - they cannot redirect or cancel
  • Do not receive a next function - the navigation is already confirmed
  • Are useful for analytics, cleanup, and side effects

Signature:

router.afterEach((to, from, failure) => {
  // called after navigation is confirmed
})

Parameters:

  • to: The target route being navigated to
  • from: The current route being navigated away from
  • failure: (optional) Navigation failure if the navigation failed

Common use cases:

  • Analytics tracking
  • Updating page titles
  • Sending telemetry
  • Detecting navigation failures

Example:

router.afterEach((to, from, failure) => {
  if (!failure) {
    // Send analytics
    analytics.sendPageView(to.fullPath)
  }
})

The key distinction: beforeEach guards can control navigation (redirect/cancel), while afterEach hooks can only react to completed navigation.

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, here's how to define route meta fields:

Route meta fields are defined by adding a meta property to route records. The meta property accepts an object containing arbitrary key-value pairs:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    meta: { requiresAuth: true }
  },
  {
    path: '/profile',
    component: UserProfile,
    meta: { requiresAuth: true, role: 'admin' }
  }
]

Accessing Meta Fields:

Meta fields can be accessed through:

  • route.meta - A non-recursive merge of all meta fields from parent to child routes
  • route.matched - Array of all matched route records, each containing its own meta property

Common Use Case - Navigation Guards:

router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    return { name: 'login' }
  }
})

TypeScript Support:

You can type-augment the RouteMeta interface to add type safety:

declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    role?: string
  }
}

Sources:

95% confidence
A

onBeforeRouteUpdate is a Composition API function in Vue Router 4 that adds a navigation guard triggered when the current route is about to be updated while staying in the same component.

Key characteristics:

  • Used in components with the Composition API (setup script or setup function)
  • Triggers when route params, query, or hash change but the component is reused
  • Can be called in any component rendered by <router-view>, not just route components
  • Must be imported from 'vue-router'

Function signature:

function onBeforeRouteUpdate(updateGuard: NavigationGuard): void

Guard parameters:

  • to: RouteLocationNormalized - target route location
  • from: RouteLocationNormalized - current route location

Usage example:

import { onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteUpdate(async (to, from) => {
  // React to route changes
  // Return false to cancel navigation
  // Return a route location to redirect
  // Return nothing/undefined to confirm navigation
  if (to.params.id !== from.params.id) {
    await fetchUser(to.params.id)
  }
})

When it triggers:
Called when navigating to a different route that renders the same component (e.g., /user/1 to /user/2 with same User component).

Differences from Options API:
This is the Composition API equivalent of the beforeRouteUpdate option. Unlike the option, it's a composable that can be used flexibly within setup.

Sources:

95% confidence
A

Navigation guards are hooks into the Vue Router navigation process that allow you to guard navigations by redirecting or canceling them. They provide multiple ways to intercept route changes: globally, per-route, or in-component.

Types of Navigation Guards

1. Global Guards:

  • router.beforeEach() - Called before every navigation, in creation order
  • router.beforeResolve() - Called before navigation is confirmed, after all in-component guards and async route components are resolved
  • router.afterEach() - Called after navigation is confirmed (cannot affect navigation)

2. Per-Route Guard:

  • beforeEnter - Defined directly in route configuration objects

3. In-Component Guards:

  • beforeRouteEnter - Called before the component is rendered
  • beforeRouteUpdate - Called when route changes but component is reused
  • beforeRouteLeave - Called when navigating away from the component

For Composition API, use onBeforeRouteUpdate() and onBeforeRouteLeave() functions.

Common Use Cases

  • Authentication/authorization checks
  • Confirming unsaved changes before leaving
  • Fetching data before entering routes
  • Analytics and page title changes
  • Redirecting based on conditions

Guards can control navigation by returning false (cancel), a route location (redirect), or nothing/true (confirm).

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's how to configure scrollBehavior:

Configure scrollBehavior as a function option when creating the router instance with createRouter(). The function receives three parameters: to (target route), from (current route), and savedPosition (previous scroll position, only available for browser back/forward).

Function signature:

scrollBehavior(to, from, savedPosition) {
  // return desired position
}

Return values:

  • { left: number, top: number } - scroll to specific coordinates
  • { el: string | Element, top?: number, left?: number, behavior?: 'smooth' | 'auto' } - scroll to element
  • false - prevent scrolling
  • A Promise resolving to any of the above (for delayed scrolling)

Example - Scroll to top:

const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    return { top: 0 }
  }
})

Example - Preserve scroll position on back/forward:

scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { top: 0 }
  }
}

Example - Scroll to anchor with smooth behavior:

scrollBehavior(to, from, savedPosition) {
  if (to.hash) {
    return {
      el: to.hash,
      behavior: 'smooth',
    }
  }
}

Key difference from Vue Router 3: The properties x and y have been renamed to left and top respectively.

Sources:

95% confidence
A

In Vue 3 Composition API, access query parameters using the useRoute() composable from Vue Router:

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // Access query parameters
    console.log(route.query.myParam)
    
    // Example: URL is /search?q=vue&filter=recent
    console.log(route.query.q)      // 'vue'
    console.log(route.query.filter) // 'recent'
  }
}

Key points:

  • Use useRoute() instead of this.$route (which doesn't exist in setup())
  • Query parameters are accessed via route.query object
  • route.query is reactive and will update when query parameters change
  • To navigate with query parameters, use useRouter() and router.push({ query: { param: 'value' } })

Accessing in <script setup> syntax:

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
const searchQuery = route.query.q
</script>

Sources:

95% confidence
A

Use useRouter() to access the router instance in Composition API:

import { useRouter } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    
    function navigateToUser(userId) {
      // String path
      router.push('/users/' + userId)
      
      // Object with path
      router.push({ path: '/users/' + userId })
      
      // Named route with params
      router.push({ name: 'user', params: { id: userId } })
      
      // With query params
      router.push({ path: '/users', query: { id: userId } })
    }
    
    return { navigateToUser }
  }
}

Key methods:

  • router.push(location) - Navigate and add history entry
  • router.replace(location) - Navigate without adding history entry
  • router.go(n) - Go forward/backward in history
  • router.back() - Go back one entry
  • router.forward() - Go forward one entry

All methods return a Promise for the navigation result.

Access current route:

import { useRoute } from 'vue-router'

const route = useRoute()
// Access: route.params, route.query, route.path, etc.

Sources:

95% confidence
A

To redirect to a different route in a navigation guard in Vue Router 4, return a route location from the guard function.

You have three options:

  1. Return a string path:
router.beforeEach((to, from) => {
  return '/login'
})
  1. Return a route location object:
router.beforeEach((to, from) => {
  return { name: 'Login' }
})
  1. Return a route location object with params:
router.beforeEach((to, from) => {
  return { path: '/login', query: { redirect: to.fullPath } }
})

Return behavior:

  • Returning a route location (string or object) → redirects to that location
  • Returning false → cancels the navigation
  • Returning undefined, true, or nothing → validates and continues navigation

This works for all navigation guard types: global guards (beforeEach, beforeResolve, afterEach), per-route guards (beforeEnter), and in-component guards (beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave).

Sources:

95% confidence
A

Return false from the navigation guard.

In Vue Router 4, you cancel navigation by explicitly returning false from any navigation guard function. This works in global guards (beforeEach, beforeResolve), per-route guards (beforeEnter), and in-component guards (beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave).

Global Guard Example:

router.beforeEach((to, from) => {
  // Cancel navigation
  return false
})

In-Component Guard Example:

import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('Do you really want to leave?')
  if (!answer) return false  // Cancels navigation
})

When navigation is canceled, if the browser URL was changed (manually by the user or via back button), it will be reset to the URL of the from route.

Note: Returning undefined or not returning anything allows navigation to proceed. Only an explicit false return value cancels the navigation.

Sources:

95% confidence
A

Based on the official Vue Router documentation search results, I can provide you with an authoritative answer:

useRouter() is a composable function in Vue Router 4 that returns the router instance when called inside a Vue 3 Composition API setup() function or <script setup>.

It provides access to the same router instance that would be available as this.$router in the Options API, but in a way that works with the Composition API where this is not accessible.

Usage:

import { useRouter } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    
    // Navigate programmatically
    router.push('/about')
    
    // Access router methods
    router.back()
    router.forward()
  }
}

In <script setup>:

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function navigateToAbout() {
  router.push('/about')
}
</script>

The composable takes no parameters and must be called inside setup() or <script setup> to work properly.

Sources:

95% confidence
A

Navigation guards in Vue Router 4 receive three parameters:

  1. to - Type: RouteLocationNormalized - The target route location being navigated to
  2. from - Type: RouteLocationNormalized - The current route location being navigated away from
  3. next (optional/legacy) - Type: NavigationGuardNext - A callback function to resolve the navigation

Modern syntax (recommended in Vue Router 4):

router.beforeEach(async (to, from) => {
  // return false to cancel navigation
  // return true or nothing to allow navigation
  // return a route location to redirect
})

Legacy syntax (still supported but discouraged):

router.beforeEach((to, from, next) => {
  next() // must call next()
})

The next parameter was common in Vue Router 3 but is now discouraged in Vue Router 4. The modern approach uses return values instead of calling next(). Returning false cancels navigation, returning true or nothing validates it, and returning a route location redirects.

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, here's how to access route meta fields in navigation guards:

Access route meta fields using to.meta or from.meta in navigation guard parameters.

In global guards:

router.beforeEach((to, from) => {
  // Access meta fields on the 'to' route
  if (to.meta.requiresAuth && !isAuthenticated) {
    return {
      name: 'login',
      query: { redirect: to.fullPath }
    }
  }
})

Key points:

  • Navigation guards receive to and from route location objects as parameters
  • Access meta fields via to.meta.propertyName or from.meta.propertyName
  • to.meta is a non-recursive merge of all meta fields from parent to child routes
  • You can check any custom property you've defined in your route's meta object

Example with matched routes:

router.beforeEach((to, from) => {
  // Check if any matched route has requiresAuth meta field
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // route requires auth, check if logged in
  }
})

The same pattern works for beforeResolve, afterEach, and per-route guards.

Sources:

95% confidence
A

onBeforeRouteLeave is a Composition API function in Vue Router 4 that adds a navigation guard triggered when the component for the current location is about to be navigated away from.

Signature:

function onBeforeRouteLeave(leaveGuard: NavigationGuard): void

The guard callback receives two route location parameters:

  • to - The target route location being navigated to
  • from - The current route location being navigated away from

Example:

import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('Do you really want to leave?')
  if (!answer) return false // Cancel navigation
})

Key characteristics:

  • Can be used in any component (unlike the Options API beforeRouteLeave which requires the component to be rendered by <router-view>)
  • Automatically removed when the component is unmounted
  • Can return false to cancel navigation or a route location to redirect
  • No access to component instance (this)
  • Works within <script setup> and Composition API components

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's how to access route parameters in Vue 3 Composition API:

Use the useRoute() composable to access route parameters inside setup():

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // Access route parameters
    const userId = route.params.id
    
    // The route object is reactive
    console.log(route.params)
    
    return {
      userId
    }
  }
}

Key points:

  • Import useRoute from vue-router
  • Call useRoute() inside setup() to get the current route object
  • Access parameters via route.params.parameterName
  • The route object is reactive, so it updates when the route changes
  • You cannot use this.$route in Composition API (no this in setup())

Alternative approach using props (decoupled from route):

// In route config
const routes = [
  { path: '/user/:id', component: User, props: true }
]

// In component
export default {
  props: ['id'],
  setup(props) {
    console.log(props.id) // Access as regular prop
  }
}

Sources:

95% confidence
A

In Vue Router 4, the catch-all route path syntax is /:pathMatch(.*)*

This breaks down as:

  • /:pathMatch - defines a parameter named pathMatch
  • (.*) - regex pattern matching any characters
  • * - makes the parameter repeatable, splitting the path into an array of segments

Example:

{
  path: '/:pathMatch(.*)*',
  name: 'NotFound',
  component: NotFound
}

Important notes:

  • The trailing * is required if you want to navigate to this route by name
  • Without the trailing *, / characters in params will be encoded when resolving/pushing
  • Navigating to /not/found produces: { params: { pathMatch: ['not', 'found'] }}
  • This replaces Vue Router 3's simple path: '*' syntax, which no longer works

Common use case (404 pages):

{
  path: '/:pathMatch(.*)*',
  name: 'NotFound',
  component: () => import('./views/NotFound.vue')
}

Sources:

95% confidence
A

Use dynamic imports in the component property of your route definition:

const routes = [
  {
    path: '/users/:id',
    component: () => import('./views/UserDetails.vue')
  }
]

Vue Router 4 treats the component as an async component that returns a Promise. The component is only fetched when the route is first accessed, then cached for subsequent visits.

Grouping components into the same chunk (webpack-specific):

const routes = [
  {
    path: '/users/:id',
    component: () => import(/* webpackChunkName: "user" */ './views/UserDetails.vue')
  },
  {
    path: '/users/:id/profile',
    component: () => import(/* webpackChunkName: "user" */ './views/UserProfile.vue')
  }
]

The magic comment /* webpackChunkName: "user" */ groups both components into the same async chunk named "user".

Key points:

  • Use arrow functions returning dynamic import()
  • Works with any bundler that supports dynamic imports (Webpack, Vite, Rollup)
  • Components are automatically code-split
  • The Promise is cached after first load

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, here's the correct syntax for named views:

In your template, use multiple <router-view> components with name attributes:

<router-view></router-view>
<router-view name="sidebar"></router-view>
<router-view name="footer"></router-view>

A <router-view> without a name defaults to "default".

In your route configuration, use the components option (plural) instead of component (singular):

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: HomeView,
        sidebar: SidebarView,
        footer: FooterView
      }
    }
  ]
})

The keys in the components object must match the name attributes of your <router-view> elements. The default key corresponds to unnamed <router-view> elements.

Sources:

95% confidence
A

In Vue Router 4, you navigate with query parameters by passing a query object to your navigation method:

Programmatic Navigation:

// Using router.push()
router.push({ path: '/search', query: { q: 'vue', category: 'tutorial' } })
// Results in: /search?q=vue&category=tutorial

// Or with a named route
router.push({ name: 'search', query: { q: 'vue' } })

Declarative Navigation:

<router-link :to="{ path: '/search', query: { q: 'vue' } }">Search</router-link>

Accessing Query Parameters:

// Options API
this.$route.query.q  // 'vue'

// Composition API
import { useRoute } from 'vue-router'
const route = useRoute()
route.query.q  // 'vue'

Key Points:

  • Query parameters are added via the query object property
  • They are automatically encoded in the URL as ?key=value&key2=value2
  • Access them through $route.query or useRoute().query
  • Works with both path and name based navigation
  • Query parameters persist across navigation unless explicitly changed

Sources:

95% confidence
A

Based on the official Vue Router documentation, here's the difference:

router.push() adds a new entry to the browser history stack. When users click the back button, they return to the previous page.

router.replace() navigates to a new URL without adding a history entry. It replaces the current entry in the history stack instead of pushing a new one.

Key difference: History stack behavior

  • router.push('/about') → User can press back to return to previous page
  • router.replace('/about') → User cannot press back to return to previous page

Code examples:

// Adds to history - user can go back
router.push('/about')

// Replaces current history entry - user cannot go back
router.replace('/about')

// Alternative: use replace option with push
router.push({ path: '/about', replace: true })

Browser API equivalents:

  • router.push()window.history.pushState()
  • router.replace()window.history.replaceState()

Use cases:

  • Use push for normal navigation where users should be able to go back
  • Use replace for redirects where you don't want the intermediate page in history (e.g., after login, redirect from old URLs)

Sources:

95% confidence
A

Based on the authoritative Vue Router documentation, here's the answer:

useRoute() is a Composition API function that returns the current route location as a reactive object. It is the Composition API equivalent of this.$route in the Options API.

Usage:

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // Access route properties
    console.log(route.params.id)
    console.log(route.query)
    console.log(route.path)
    
    return { route }
  }
}

Key characteristics:

  • Must be called inside setup() or <script setup>
  • Returns a reactive RouteLocationNormalizedLoaded object
  • Provides access to route properties: params, query, path, name, hash, meta, matched, fullPath
  • The route object is reactive - avoid watching the entire object; instead watch specific properties you need

Important: Because Composition API components don't have access to this, you cannot use this.$route. Use useRoute() instead.

Sources:

95% confidence
A

Based on the official Vue Router documentation, by default (when no scrollBehavior function is configured), Vue Router does not manipulate scroll position. The browser's native scroll behavior applies - the page stays at its current scroll position when navigating between routes.

This means:

  • When navigating forward to a new route: scroll position is preserved wherever it was
  • When using browser back/forward buttons: the browser's default behavior applies (typically restoring the previous scroll position in some browsers, but not guaranteed)

To customize this behavior, you must explicitly provide a scrollBehavior function when creating the router:

import { createRouter } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // Return desired position
    // Example: always scroll to top
    return { top: 0 }
  }
})

Common configurations:

  • return { top: 0 } - Always scroll to top on navigation
  • return savedPosition || { top: 0 } - Restore position for back/forward, scroll to top otherwise
  • return false or empty object - No scrolling

Sources:

95% confidence
A

In Vue Router 4, you create a router instance using the createRouter() function from the vue-router package.

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // your route definitions
  ]
})

Required steps:

  1. Import the createRouter function and a history mode (e.g., createWebHistory)
  2. Call createRouter() with a configuration object containing:
    • history: A history mode instance (required)
    • routes: Array of route definitions (required)
  3. Register the router with your Vue app using app.use(router)

Complete example:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

const app = createApp(App)
app.use(router)
app.mount('#app')

Sources:

95% confidence
A

Based on the official Vue Router documentation, I have sufficient information to provide a verified answer:


beforeEach is a global navigation guard in Vue Router 4 that executes before every route navigation. It's registered on the router instance and called in creation order whenever any navigation is triggered.

Signature:

router.beforeEach((to, from) => {
  // guard logic
})

Parameters:

  • to: The target route location being navigated to
  • from: The current route location being navigated away from

Return values:

  • undefined, true, or nothing: Validates the navigation and proceeds to the next guard
  • false: Cancels the navigation
  • A route location: Redirects to a different location

Key characteristics:

  • Guards are resolved asynchronously - navigation is pending until all guards resolve
  • Supports async functions and Promises (async/await)
  • Returns a function that removes the registered guard when called
  • Called in the order they were registered
  • Executes before any component guards or in-component guards

Example:

const router = createRouter({ ... })

router.beforeEach(async (to, from) => {
  if (!isAuthenticated && to.name !== 'Login') {
    return { name: 'Login' }
  }
})

Sources:

95% confidence
A

Based on the official Vue Router documentation, here are the key differences:

URL Format

createWebHistory() - Creates clean URLs matching the HTML5 History API:

  • https://example.com/user/id
  • Normal-looking paths without hash symbols

createWebHashHistory() - Uses hash mode with # character:

  • https://example.com/#/user/id
  • URL portion after # is used for routing

Server Configuration

createWebHistory():

  • Requires server configuration - needs a catch-all fallback route
  • Without proper server setup, users get 404 errors when accessing URLs directly
  • Server must redirect all routes to index.html

createWebHashHistory():

  • No server configuration needed - works out of the box
  • The hash portion is never sent to the server, so direct URL access works automatically

SEO & Search Engines

createWebHistory():

  • Full URLs are visible to search engines
  • Better for SEO

createWebHashHistory():

  • Hash portion is not processed by search engines
  • Poor SEO performance

Recommendation

The official Vue Router documentation recommends createWebHistory() for most web applications due to cleaner URLs and better SEO, despite requiring server configuration.

Sources:

95% confidence
A

In Vue Router 4, create a 404 catch-all route using the /:pathMatch(.*) pattern with a custom regex parameter:

const routes = [
  // ... other routes
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]

Key points:

  • The old Vue Router 3 syntax using * or /* is no longer supported
  • Use /:pathMatch(.*) as the parameter name with a regex capturing everything
  • Add * after the regex to make the matched parameter an array: /:pathMatch(.*)*
  • This should be placed as the last route in your routes array
  • The matched path segments are available in route.params.pathMatch

Example with redirect:

const routes = [
  { path: '/:pathMatch(.*)*', redirect: '/404' },
  { path: '/404', component: NotFoundComponent }
]

Sources:

95% confidence
A

router.beforeResolve() is a global navigation guard that executes before navigation is resolved, after all in-component guards and async route components have been resolved, but before the navigation is confirmed.

When it fires: After beforeEach and after all component guards (beforeRouteEnter, beforeRouteUpdate) have been called and all async route components have been fetched.

Key characteristics:

  • Called right before navigation is confirmed
  • All components have been fetched at this point
  • All other navigation guards have successfully completed
  • Ideal for fetching data or performing operations that should only happen if the user can definitely enter a page

Usage:

router.beforeResolve((to, from) => {
  // Fetch data or perform final checks
  // This only runs if all guards have passed
})

Returns: A function to remove the registered guard.

Use case: This is the ideal spot to fetch data or do any operation you want to avoid doing if the user cannot enter a page. Since all guards have passed by this point, you know the navigation will succeed.

Sources:

95% confidence
A

Based on the official Vue Router 4 documentation, here is the correct syntax for named routes:

Defining Named Routes

Add a name property to the route configuration:

const routes = [
  {
    path: '/user/:id',
    name: 'user',
    component: User
  }
]

Navigating to Named Routes

Using <router-link>:

<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>

Using programmatic navigation:

router.push({ name: 'user', params: { id: 123 }})

Key Rules

  • Each route name must be unique across all routes
  • If you add the same name to multiple routes, the router will only keep the last one
  • Named routes automatically handle encoding/decoding of params

Advantages

  • No hardcoded URLs
  • Automatic encoding/decoding of params
  • Prevents typos in the URL path
  • Bypasses path ranking (e.g., to display a sibling route)

Sources:

95% confidence
A

Based on the official Vue Router documentation, I have a verified answer:

createMemoryHistory() creates an in-memory based history that doesn't interact with the browser URL. It's primarily used for:

  1. Server-Side Rendering (SSR) - The main use case, as it works in Node.js environments where there's no browser URL
  2. Testing - Useful for unit/integration tests where you don't need actual browser navigation
  3. Non-browser environments - Any scenario where URL manipulation isn't needed or available

Key characteristics:

  • Does not read from or write to the browser URL
  • Doesn't automatically trigger initial navigation
  • Starts at a special "nowhere" location
  • Requires manual router.push() or router.replace() call after app.use(router) to set the initial location

Example:

import { createRouter, createMemoryHistory } from 'vue-router'

const router = createRouter({
  history: createMemoryHistory(),
  routes: [...]
})

For browser applications, use createWebHistory() or createWebHashHistory() instead.

Sources:

95% confidence

Components

34 questions
A

Use v-bind="$attrs" on the target element after disabling automatic inheritance with inheritAttrs: false.

Step 1: Disable automatic attribute inheritance

<script>
export default {
  inheritAttrs: false
}
</script>

Or with <script setup>:

<script setup>
defineOptions({
  inheritAttrs: false
})
</script>

Step 2: Apply $attrs to your chosen element

<template>
  <div class="wrapper">
    <input v-bind="$attrs" />
  </div>
</template>

The v-bind directive without an argument binds all properties of the $attrs object as attributes to the target element. The $attrs object contains all attributes passed to the component that are not declared as props or emits (including class, style, and event listeners).

Complete example:

<template>
  <div class="date-picker">
    <input type="datetime-local" v-bind="$attrs" />
  </div>
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

Sources:

95% confidence
A

In Vue 3, v-if has higher precedence than v-for when both directives are used on the same element.

This is a breaking change from Vue 2, where v-for had higher precedence than v-if.

Key implications:

  • The v-if condition is evaluated first
  • The v-if expression does NOT have access to variables from the v-for scope
  • Using both on the same element is not recommended due to syntax ambiguity

Recommended solutions:

  1. To conditionally filter items: Use a computed property instead
<!-- Avoid -->
<li v-for="user in users" v-if="user.isActive">

<!-- Instead -->
<li v-for="user in activeUsers">
  1. To conditionally render the entire list: Move v-if to a wrapper element
<!-- Avoid -->
<li v-for="user in users" v-if="shouldShowUsers">

<!-- Instead -->
<ul v-if="shouldShowUsers">
  <li v-for="user in users">
</ul>

Sources:

95% confidence
A

While technically possible, it is strongly discouraged to use v-if and v-for on the same element in Vue 3.

What happens if you do it:

In Vue 3, v-if has higher precedence than v-for. This means the v-if condition will not have access to variables from the v-for scope, which typically causes errors.

<!-- ❌ This will error - `item` is not available to v-if -->
<div v-for="item in items" v-if="item.isActive">
  {{ item.name }}
</div>

Recommended approaches:

1. For filtering lists - Use a computed property:

<script setup>
import { computed } from 'vue'

const activeItems = computed(() => items.filter(item => item.isActive))
</script>

<template>
  <div v-for="item in activeItems" :key="item.id">
    {{ item.name }}
  </div>
</template>

2. For conditional list rendering - Move v-if to a wrapper element:

<ul v-if="shouldShowItems">
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

3. Alternative - Use a wrapper <template>:

<template v-for="item in items" :key="item.id">
  <div v-if="item.isActive">
    {{ item.name }}
  </div>
</template>

Sources:

95% confidence
A

In Vue 3, you define default values for inject() by passing a second argument:

Static Default Values

const value = inject('key', 'default-value')

The second parameter is the default value used when no matching provide key is found.

Factory Function Defaults

For expensive-to-create defaults, use a factory function with true as the third argument:

const obj = inject('key', () => new ExpensiveObject(), true)

The third parameter (true) tells Vue to treat the function as a factory, not as the literal default value.

Type Signatures

// Without default (may be undefined)
inject<T>(key: InjectionKey<T> | string): T | undefined

// With static default
inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// With factory function
inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

The default value removes undefined from the return type when using TypeScript.

Sources:

95% confidence
A

Wrap the value in ref() or reactive() before providing it.

import { ref, provide } from 'vue'

// Using ref
const count = ref(0)
provide('count', count)

// Using reactive
import { reactive, provide } from 'vue'

const state = reactive({ count: 0 })
provide('state', state)

The provided ref or reactive object maintains its reactivity when injected in descendant components. The injected value automatically updates when the provider changes it.

Best practice: Provide mutation functions alongside reactive values to keep state changes centralized in the provider:

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})

Options API: Use computed() to ensure reactivity:

export default {
  provide() {
    return {
      message: computed(() => this.message)
    }
  }
}

Sources:

95% confidence
A

In Vue 3, you define a component with multiple v-model bindings by using named v-model arguments. Each v-model targets a different prop.

Parent component usage:

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

Child component definition (Vue 3.4+):

<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

Pre-Vue 3.4 approach:

<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Each v-model syncs to a different prop without requiring extra configuration. The syntax v-model:argumentName replaces the deprecated .sync modifier from Vue 2.

Sources:

95% confidence
A

Use onErrorCaptured() hook or errorCaptured lifecycle option in the parent component containing <Suspense>.

<Suspense> does not provide error handling via the component itself. When async components fail inside Suspense, you must handle errors at the parent level:

Composition API:

<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((err, instance, info) => {
  // Handle the error from async component
  console.error('Async component error:', err)
  // Return false to prevent error propagation
  return false
})
</script>

<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Options API:

<script>
export default {
  errorCaptured(err, instance, info) {
    // Handle the error
    console.error('Async component error:', err)
    return false // Prevent propagation
  }
}
</script>

<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Important: When async components are used with Suspense (they are suspensible by default), their own errorComponent, loading, and timeout options are ignored. Error handling must be done via the parent's error capture hooks.

Alternative: Set suspensible: false in defineAsyncComponent() to opt-out of Suspense control and use the component's own errorComponent option instead.

Sources:

95% confidence
A

In Vue 3, you emit events with payload data by passing additional arguments to the $emit() function after the event name. All extra arguments are forwarded to the listener.

Composition API (Script Setup):

<script setup>
const emit = defineEmits(['increaseBy'])

function buttonClick() {
  emit('increaseBy', 1)  // Emit event with payload
}
</script>

Options API:

export default {
  emits: ['increaseBy'],
  methods: {
    submit() {
      this.$emit('increaseBy', 1)  // Emit event with payload
    }
  }
}

Parent Component Receiving Payload:

<!-- Inline arrow function -->
<MyButton @increase-by="(n) => count += n" />

<!-- Or method reference -->
<MyButton @increase-by="increaseCount" />

Multiple Arguments:

emit('eventName', arg1, arg2, arg3)
// Listener receives all three arguments as separate parameters

Sources:

95% confidence
A

defineAsyncComponent in Vue 3 accepts either a simple loader function or an options object with the following properties:

Options Object Properties:

  • loader (required): Function that returns a Promise resolving to the component definition
  • loadingComponent: Component to display while the async component is loading
  • errorComponent: Component to display if the load fails
  • delay: Number (milliseconds) before showing the loading component. Default: 200
  • timeout: Number (milliseconds) before timing out and showing error component. Default: Infinity
  • suspensible: Boolean controlling whether the component should be controllable by Suspense. Default: true
  • onError: Function for custom error handling with signature (error, retry, fail, attempts) => any

Usage Example:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./MyComponent.vue'),
  loadingComponent: LoadingComponent,
  delay: 200,
  errorComponent: ErrorComponent,
  timeout: 3000,
  suspensible: false,
  onError(error, retry, fail, attempts) {
    if (attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})

Sources:

95% confidence
A

The Vue 3 <Suspense> component has exactly two slots:

  1. #default - The primary content slot containing components with async dependencies
  2. #fallback - The loading/fallback content displayed while async dependencies are resolving

Both slots only allow one immediate child node. The default slot content is shown when all async dependencies are resolved; otherwise, the fallback slot content is displayed during the pending state.

Example:

<Suspense>
  <!-- component with nested async dependencies -->
  <Dashboard />
  
  <!-- loading state via #fallback slot -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

Sources:

95% confidence
A

In Vue 3, v-model uses modelValue as the default prop name and update:modelValue as the default event name.

Default v-model:

  • Prop: modelValue
  • Event: update:modelValue

When you write v-model="foo", it expands to:

:modelValue="foo" @update:modelValue="$event => (foo = $event)"

Custom v-model with arguments:

For v-model:title="bookTitle":

  • Prop: title
  • Event: update:title

Pattern: The prop name is the argument itself, and the event name is update:{propName}.

Component implementation example:

<!-- Child component -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Modifier props:

  • Default v-model modifiers: modelModifiers prop
  • Custom argument modifiers: {propName}Modifiers prop (e.g., titleModifiers for v-model:title.capitalize)

Sources:

95% confidence
A

The shorthand syntax for v-slot in Vue 3 is the hash symbol (#).

Basic Syntax

Replace v-slot: with #:

<!-- Full syntax -->
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<!-- Shorthand -->
<template #header>
  <h1>Here might be a page title</h1>
</template>

With Scoped Slots

<template #default="slotProps">
  {{ slotProps.message }}
</template>

With Dynamic Slot Names

<template #[dynamicSlotName]>
  ...
</template>

Important Limitation

The shorthand is only available when an argument is provided. You must specify the slot name even for the default slot when using the shorthand (e.g., #default).

Sources:

95% confidence
A

In Vue 3, scoped slots use the v-slot directive (shorthand #) to receive props from child components.

Default scoped slot:

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

With destructuring:

<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

Named scoped slots:

<MyComponent>
  <template #header="headerProps">
    {{ headerProps.title }}
  </template>
  
  <template #default="{ text }">
    {{ text }}
  </template>
</MyComponent>

Defining in child component:

<template>
  <slot :text="message" :count="1"></slot>
  <slot name="header" :title="headerTitle"></slot>
</template>

Key syntax rules:

  • Use v-slot="propName" or shorthand #default="propName" for default slot
  • Use v-slot:slotName="propName" or shorthand #slotName="propName" for named slots
  • v-slot must be used on <template> tags (except when component has only default slot)
  • Destructuring works: v-slot="{ prop1, prop2 }"

Sources:

95% confidence
A

The h function in Vue 3 is a utility for creating virtual DOM nodes (vnodes) programmatically. The "h" stands for "hyperscript" – JavaScript that produces HTML.

Function signature:

function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// props can be omitted
function h(type: string | Component, children?: Children | Slot): VNode

Parameters:

  • type - String for HTML elements (like 'div') or a Vue component definition
  • props (optional) - Object with attributes, properties, event handlers
  • children (optional) - Child content: string, number, VNode, array of VNodes, or slot functions

Import in Vue 3:

import { h } from 'vue'

Usage examples:

Creating HTML elements:

h('div')
h('div', { id: 'foo' })
h('div', { class: 'bar', innerHTML: 'hello' })
h('div', 'hello')
h('div', [h('span', 'hello')])

Creating components:

h(MyComponent, { someProp: 'hello', onUpdate: () => {} })
h(MyComponent, () => 'default slot')
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo')
})

Sources:

95% confidence
A

defineComponent() is a type helper function in Vue 3 that enables TypeScript to properly infer types inside component options. It's essentially a runtime no-op—it returns the same options object you pass in—but provides critical type inference for TypeScript users.

When to use it:

  1. TypeScript projects with Options API - Required to get proper type inference for this in component options
  2. TypeScript projects with Composition API using object syntax - Helps with type inference when using the object-based component definition
  3. Render functions or JSX with TypeScript - Necessary for type inference when the component is used as a tag in TSX

When you DON'T need it:

  • Using <script setup> - This has built-in type inference without needing defineComponent()
  • JavaScript projects (no TypeScript) - No type inference benefit, though it doesn't hurt

Basic usage example:

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    message: String
  },
  setup(props) {
    // props.message is properly typed as string | undefined
  }
})

Function signature (Vue 3.3+):

import { defineComponent } from 'vue'

export default defineComponent((props) => {
  // Composition API setup function
  return () => {
    // render function
  }
})

Sources:

95% confidence
A

Use defineProps() with a TypeScript generic type parameter:

<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

Alternative: Runtime Declaration

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})
</script>

With Interface:

<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

With Default Values:

<script setup lang="ts">
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']  // wrap arrays/objects in functions
})
</script>

Key Points:

  • Type-based declaration (generic syntax) is the recommended approach
  • Optional props use ? in the type definition
  • Default values require the withDefaults() macro with type-based declaration
  • Complex reference types (arrays, objects) should be wrapped in functions when providing defaults

Sources:

95% confidence
A

In Vue 3 render functions, access the default slot content by calling slots.default() (Composition API) or this.$slots.default() (Options API). Each slot is a function that returns an array of vnodes.

Composition API:

export default {
  setup(props, { slots }) {
    return () => h('div', slots.default())
  }
}

Options API:

export default {
  render() {
    return h('div', this.$slots.default())
  }
}

With JSX:

// Composition API
export default {
  setup(props, { slots }) {
    return () => <div>{slots.default()}</div>
  }
}

// Options API
export default {
  render() {
    return <div>{this.$slots.default()}</div>
  }
}

The slots object is accessed from the setup context in Composition API, while in Options API it's available as this.$slots.

Sources:

95% confidence
A

In Vue 3, validate emitted events by using the object syntax for the emits option instead of the array syntax. Assign each event a validator function that receives the emitted arguments and returns true (valid) or false (invalid).

Composition API (<script setup>):

const emit = defineEmits({
  // No validation
  click: null,

  // Validate submit event
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}

Options API:

export default {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}

The validator function executes at runtime when the event is emitted. Returning false does not prevent the event from being emitted but allows you to log warnings or perform validation logic.

Sources:

95% confidence
A

<Suspense> is a built-in Vue 3 component that orchestrates async dependencies in a component tree. It displays a loading state (fallback) while waiting for multiple nested async operations to resolve.

Status: Experimental (API may change before stable release)

What Async Dependencies It Handles

  1. Components with async setup() hooks (including <script setup> with top-level await)
  2. Async Components (defined using defineAsyncComponent)

Basic Usage

<Suspense>
  <!-- default slot: component with async dependencies -->
  <Dashboard />
  
  <!-- fallback slot: shown while loading -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

With Async Setup

<script setup>
// Top-level await makes this component an async dependency
const res = await fetch('/api/user')
const user = await res.json()
</script>

<template>
  <div>{{ user.name }}</div>
</template>

Then wrap it:

<Suspense>
  <UserProfile />
  <template #fallback>
    Loading user...
  </template>
</Suspense>

Key Properties

  • Two slots required: #default (main content) and #fallback (loading state)
  • One child per slot: Each slot accepts only one immediate child node
  • Events: Emits pending, resolve, and fallback events
  • timeout prop: Controls delay before showing fallback (default: 0ms)

Error Handling

Suspense doesn't handle errors automatically. Use errorCaptured hook or onErrorCaptured():

<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((err) => {
  // handle error
  return false // prevent propagation
})
</script>

Sources:

95% confidence
A

The disabled prop controls whether the Teleport component moves its content to the target location or keeps it in its original place in the DOM.

When disabled is true, the content remains in its original location instead of being teleported to the target container. When false (default), the content is moved to the target.

The prop can be changed dynamically, making it useful for responsive designs where you want different behavior based on conditions:

<Teleport :disabled="isMobile">
  <video src="./my-movie.mp4">
</Teleport>

In this example, the video would render inline when isMobile is true (content stays in place), but teleport to the target when isMobile is false (content moves to target).

Type: boolean
Default: false

Sources:

95% confidence
A

In Vue 3, custom v-model modifiers are accessed through the modelModifiers prop, which provides an object containing all applied modifiers as boolean flags.

Modern approach (Vue 3.4+) - Use defineModel() with a setter:

const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})

Traditional approach - Accept modelModifiers prop:

export default {
  props: {
    modelValue: String,
    modelModifiers: { default: () => ({}) }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}

With named v-models, modifiers follow the pattern [argName]Modifiers:

const [firstName, firstNameModifiers] = defineModel('firstName')
// Access via firstNameModifiers.capitalize

Usage:

<MyComponent v-model.capitalize="text" />
<UserName v-model:first-name.capitalize="first" />

Sources:

95% confidence
A

In Vue 3 Options API, define props with type checking using the props option with object syntax:

Basic type checking:

export default {
  props: {
    title: String,
    count: Number,
    isActive: Boolean
  }
}

Full validation syntax:

export default {
  props: {
    // Single type
    age: Number,
    
    // Multiple possible types
    id: [String, Number],
    
    // Required with type
    name: {
      type: String,
      required: true
    },
    
    // With default value
    count: {
      type: Number,
      default: 0
    },
    
    // Object/Array defaults use factory function
    settings: {
      type: Object,
      default() {
        return { theme: 'dark' }
      }
    },
    
    // Custom validator
    status: {
      type: String,
      validator(value) {
        return ['active', 'pending', 'inactive'].includes(value)
      }
    }
  }
}

Available types: String, Number, Boolean, Array, Object, Date, Function, Symbol, Error, custom constructor functions, or arrays of these.

Vue validates props in development mode and warns in the console when types don't match.

For TypeScript: Wrap component with defineComponent() for type inference, or use PropType utility for complex types:

import { defineComponent, type PropType } from 'vue'

export default defineComponent({
  props: {
    user: {
      type: Object as PropType<{ name: string; age: number }>,
      required: true
    }
  }
})

Sources:

95% confidence
A

The Teleport component in Vue 3 is a built-in component that renders a part of a component's template into a DOM node outside the component's DOM hierarchy.

Purpose: Teleport solves the problem of rendering content (like modals, tooltips, or notifications) at a different location in the DOM tree while keeping the component logic together.

Basic Syntax:

<Teleport to="body">
  <div class="modal">I'm rendered in the body element</div>
</Teleport>

Why it's needed:

  • CSS positioning issues: When a parent has transform, perspective, or filter CSS properties, position: fixed children won't position relative to the viewport. Teleporting to body fixes this.
  • Z-index stacking: Nested elements inherit stacking contexts from parents, causing z-index conflicts. Teleporting to the root avoids these issues.
  • Modal/overlay patterns: Keeps modal button and modal content in the same component for better code organization, while rendering the modal at the document root for proper styling.

Key Props:

  • to: CSS selector string or DOM element (required)
  • disabled: Boolean to toggle teleporting on/off
  • defer: (Vue 3.5+) Defers target resolution until after app mount

Important: Teleport only changes where content is rendered in the DOM. The logical component hierarchy remains unchanged—props, events, and provide/inject still work normally.

Sources:

95% confidence
A

inheritAttrs is a boolean component option in Vue 3 that controls whether attributes passed from a parent component automatically fall through to the child component's root element.

Default value: true

Type: boolean

Behavior:

  • When true (default): Attributes not declared as props are automatically applied to the component's root element
  • When false: Automatic fallthrough is disabled, and attributes remain accessible via $attrs for manual application

When to use inheritAttrs: false:
When you want to apply parent attributes to a non-root element instead of the root element. This is common in wrapper components.

Example with Options API:

export default {
  inheritAttrs: false,
  mounted() {
    console.log(this.$attrs) // Access fallthrough attributes
  }
}

Example with <script setup> (Vue 3.3+):

<script setup>
import { useAttrs } from 'vue'

defineOptions({
  inheritAttrs: false
})

const attrs = useAttrs()
</script>

<template>
  <div class="wrapper">
    <!-- Apply attrs to inner element instead of root -->
    <button v-bind="attrs">Click me</button>
  </div>
</template>

Note: In Vue 3, $attrs includes class and style (unlike Vue 2 where these were excluded), making it easier to forward all attributes to any element.

Sources:

95% confidence
A

In Vue 3 Composition API, provide() and inject() enable dependency injection between ancestor and descendant components.

provide()

Syntax:

provide(key, value)
  • key: string or Symbol
  • value: any value, including reactive refs

Example:

import { ref, provide } from 'vue'

// Provide a static value
provide('message', 'hello')

// Provide a reactive value
const count = ref(0)
provide('count', count)

// Provide with Symbol key (recommended for libraries)
import { myInjectionKey } from './keys'
provide(myInjectionKey, count)

inject()

Syntax:

// Without default
inject(key)

// With default value
inject(key, defaultValue)

// With factory function
inject(key, factoryFn, true)

Example:

import { inject } from 'vue'

// Basic injection
const message = inject('message')

// With default value
const count = inject('count', 0)

// With factory function for expensive defaults
const obj = inject('key', () => new ExpensiveClass(), true)

Important notes:

  • Must be called synchronously during setup() or <script setup>
  • Injected refs remain reactive (not auto-unwrapped)
  • Vue walks up the parent chain to find the matching key
  • Returns undefined if no provider found (unless default specified)

Sources:

95% confidence
A

The purpose of provide and inject in Vue 3 is to solve prop drilling — passing data through multiple intermediate components that don't need it just to reach a deeply nested child component.

How it works:

  • A parent component uses provide() to make data/dependencies available to all its descendants
  • Any descendant component (regardless of depth) can use inject() to access the provided values directly
  • Intermediate components don't need to know about or pass along this data

Key benefits:

  • Eliminates passing props through components that don't use them
  • Creates a dependency injection pattern for component trees
  • Maintains reactivity when providing refs (injected values stay reactive)
  • Works at app-level (app.provide()) to make data available to all components

Example:

// Parent component (provider)
import { provide, ref } from 'vue'

const message = ref('Hello')
provide('message', message)

// Descendant component (injector)
import { inject } from 'vue'

const message = inject('message')

Sources:

95% confidence
A

defineAsyncComponent is a Vue 3 function that defines an async component which is lazy loaded only when it is rendered. This enables code splitting to reduce initial bundle size in large applications.

Basic Usage

The simplest form uses ES module dynamic import:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

The component will only be loaded when it's actually rendered in your template.

Advanced Usage with Options

For loading states and error handling:

const AsyncComp = defineAsyncComponent({
  loader: () => import('./components/MyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,        // ms before showing loading component
  timeout: 3000      // ms before showing error component
})

Full Type Signature

function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component

type AsyncComponentLoader = () => Promise<Component>

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}

Configuration Options

  • loader - Function returning Promise that resolves to component
  • loadingComponent - Component to display while loading (default: none)
  • delay - Milliseconds before showing loading component (default: 200)
  • errorComponent - Component to display on load failure (default: none)
  • timeout - Milliseconds before timeout error (default: Infinity)
  • suspensible - Whether component works with <Suspense> (default: true)
  • onError - Error handler with retry/fail callbacks and attempt counter

Registration Examples

Global:

app.component('MyComponent', defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
))

Local (Composition API):

<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)
</script>

<template>
  <AdminPage />
</template>

Sources:

95% confidence
A

In Vue 3, define required props by setting required: true in the prop definition object.

Options API:

export default {
  props: {
    propName: {
      type: String,
      required: true
    }
  }
}

Composition API (<script setup>):

<script setup>
defineProps({
  propName: {
    type: String,
    required: true
  }
})
</script>

All props are optional by default unless required: true is specified. An absent required prop will trigger a warning in the browser's JavaScript console.

You can combine required with other validations:

propName: {
  type: [String, Number],
  required: true,
  validator: (value) => value.length > 0
}

Sources:

95% confidence
A

In Vue 3, you define a functional component as a plain JavaScript function that receives props and a context object:

function MyComponent(props, context) {
  // return vnode
}

Two arguments:

  1. props - Component props (first argument)
  2. context - Object containing { slots, emit, attrs } (equivalent to $slots, $emit, $attrs)

Declaring props and emits:

MyComponent.props = ['message', 'count']
MyComponent.emits = ['update']

Complete example:

function MyButton(props, { emit }) {
  return h('button', {
    onClick: () => emit('click')
  }, props.text)
}

MyButton.props = ['text']
MyButton.emits = ['click']

Functional components are stateless - they have no reactive state, lifecycle hooks, or this context. They act as pure functions: props in, vnodes out.

Note: In Vue 2, you used the functional: true option or functional attribute. In Vue 3, this approach was removed. All functional components are now plain functions.

Sources:

95% confidence
A

In Vue 3 Composition API, use defineEmits() to declare and emit events.

With <script setup> (recommended):

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

With explicit setup() function:

export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}

Emitting with arguments:

emit('submit', { email: '[email protected]', password: '123' })

TypeScript type-safe syntax:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

Important: defineEmits() must be called directly within <script setup>, not inside functions.

Sources:

95% confidence
A

Fallback content in slots is default content that renders only when the parent component doesn't provide any content for that slot.

You define it by placing content between the <slot> tags in your component:

<button type="submit">
  <slot>
    Submit <!-- fallback content -->
  </slot>
</button>

Behavior:

  • When parent provides no content: <SubmitButton /> → renders "Submit"
  • When parent provides content: <SubmitButton>Save</SubmitButton> → renders "Save"

Parent-provided content always takes precedence over fallback content.

Sources:

95% confidence
A

In Vue 3, you define props with validator functions by adding a validator property to the prop configuration. The validator receives the prop value (and optionally the full props object in Vue 3.4+) and returns a boolean.

Syntax:

defineProps({
  status: {
    type: String,
    validator(value, props) {
      // return true if valid, false if invalid
      return ['success', 'warning', 'danger'].includes(value)
    }
  }
})

Key Details:

  • The validator function receives two parameters:
    • value - the prop value being validated
    • props - full props object (available in Vue 3.4+)
  • Must return true for valid, false for invalid
  • Props are validated before the component instance is created, so you cannot access data, computed, methods, or other instance properties inside validators
  • Failed validation produces a console warning in development builds

Example with Options API:

export default {
  props: {
    status: {
      type: String,
      validator(value) {
        return ['success', 'warning', 'danger'].includes(value)
      }
    }
  }
}

Sources:

95% confidence
A

The to prop in Vue's Teleport component specifies the target container where the content will be rendered.

Type: string | HTMLElement

Accepted values:

  • CSS selector string (e.g., "#some-id", ".some-class", "body", "[data-teleport]")
  • Actual DOM element (an HTMLElement reference)

The prop is required for the Teleport component to function.

Example:

<Teleport to="body">
  <div class="modal">Modal content</div>
</Teleport>

<!-- Or with a DOM reference -->
<Teleport :to="domElement">
  <div>Content</div>
</Teleport>

Sources:

95% confidence
A

In Vue 3, named slots are defined using the v-slot directive on a <template> element:

Full syntax:

<template v-slot:slotName>
  <!-- slot content -->
</template>

Shorthand syntax (recommended):

<template #slotName>
  <!-- slot content -->
</template>

Complete example:

<BaseLayout>
  <template #header>
    <h1>Page Title</h1>
  </template>

  <template #default>
    <p>Main content</p>
  </template>

  <template #footer>
    <p>Footer content</p>
  </template>
</BaseLayout>

Dynamic slot names:

<template #[dynamicSlotName]>
  <!-- content -->
</template>

The # symbol is the shorthand for v-slot:, just as @ is shorthand for v-on: and : is shorthand for v-bind:.

Sources:

95% confidence

unknown

33 questions
A

Options API: 'define components using object with options (data, methods, computed, watch, lifecycle), original Vue API style.' Structure: export default { data(), methods: {}, computed: {}, watch: {}, mounted() {}, etc. }. Benefits: easier learning curve, organized by option type, suitable for simple components. Composition API: function-based, organized by logical concern. Key differences: (1) Organization: Options by type, Composition by feature. (2) Reusability: Options (mixins, problematic), Composition (composables, clean). (3) TypeScript: Options harder, Composition excellent. (4) Bundle size: Composition smaller (tree-shakeable). (5) Learning: Options easier, Composition more flexible. Both work in Vue 3. Choose: Options for simple, Composition for complex/reusable. Can mix both. Essential to understand both.

Sources
95% confidence
A

v-model: 'creates two-way binding on form inputs and components, syncs input value with data.' Mechanism: syntactic sugar for :value + @input (v-bind + v-on). Example: equivalent to <input :value='message' @input='message = $event.target.value'>. Works with: text, textarea, checkbox, radio, select. Modifiers: .lazy (change instead of input), .number (parse as number), .trim (trim whitespace). Component v-model: custom components can use v-model with defineModel() or props/emits pattern. Multiple v-models: v-model:propName. Use for: forms, user input, component communication. Understanding essential for forms and state management.

Sources
95% confidence
A

setup(): 'entry point for Composition API in component, executed before component creation.' Timing: runs once during component initialization, before created hook. Returns: object exposed to template, or render function. Parameters: props (reactive), context (attrs, slots, emit). 'We can declare reactive state using Reactivity APIs and expose them to template by returning object from setup().' No this: use props, context instead. Lifecycle: use onMounted() etc. . Normal