Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Vue 3 Composition API Deep Dive

By Kokil Thapa | Last reviewed: September 2026

A Vue 3 Composition API deep dive matters when your components outgrow the Options API. Logic gets split across data, methods, and mounted hooks. Related code ends up scattered. The Composition API groups behaviour by feature instead of option type. On Laravel Blade apps and small SPAs I maintain, that shift keeps complex UI state readable. This guide walks through setup, reactivity, composables, and patterns that survive production.

What Is the Vue 3 Composition API and Why Use It?

The Composition API is a set of functions for building Vue components. It lives alongside the Options API. You import helpers from Vue and use them inside setup() or <script setup>. For a full Laravel pairing guide, see the Vue with Laravel setup complete guide.

Vue 3 shipped the Composition API as stable in 2021. In 2026 it is the default choice for new components in most teams. Options API still works. Vue does not plan to remove it.

Three problems pushed teams toward Composition API:

  • Logic fragmentation. A search feature might touch data, computed, methods, and watch blocks far apart in one file.
  • Limited reuse. Mixins and HOCs caused naming collisions and unclear data sources.
  • Weak TypeScript inference. Options API typing often needs extra boilerplate.
Vue 3 Composition API StructureOptions APIdata() — all state mixedmethods — all handlers mixedwatch — scattered watchersmounted — side effectsComposition APIuseSearch()query, results, fetchusePagination()page, total, nextuseAuth()user, login, logout
Vue 3 Composition API groups related logic into composables instead of splitting it across option blocks.

On booking dashboards I have shipped with Adventure Third Pole Trek, filters, pagination, and cart state each became a composable. New pages imported the same functions. That cut duplicate code without mixin side effects.

How Do You Set Up Vue 3 Composition API Components?

Two entry styles exist. Explicit setup() returns an object or render function. <script setup> is syntactic sugar that compiles to setup. Most new code uses script setup because it is shorter.

Script setup baseline

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

const query = ref('')
const results = ref([])

const hasQuery = computed(() => query.value.trim().length > 0)

async function search() {
  const response = await fetch(`/api/search?q=${encodeURIComponent(query.value)}`)
  results.value = await response.json()
}

onMounted(() => {
  if (window.location.search.includes('q=')) {
    query.value = new URLSearchParams(window.location.search).get('q') ?? ''
    search()
  }
})
</script>

<template>
  <input v-model="query" @keyup.enter="search" />
  <ul v-if="hasQuery">
    <li v-for="item in results" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

Top-level bindings in script setup are automatically exposed to the template. You do not return them manually. That removes a common source of "undefined in template" bugs.

Explicit setup when you need it

Use explicit setup(props, context) when integrating with non-standard build tools or when you must return a render function. For typical Blade-mounted widgets, script setup is enough.

Install Vue 3 with Vite 8.x and npm 12 on Node.js 26 LTS. A minimal Vite scaffold:

npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install
npm run dev

Pair that with the patterns in our Laravel Blade components deep dive when embedding islands of interactivity inside server-rendered pages.

Vue 3 Composition API Deep Dive: ref, reactive, and computed

Reactivity is the core of any Vue 3 Composition API deep dive. Vue 3 uses Proxies. You wrap values in ref() or reactive(). The runtime tracks reads and writes. Dependent computed properties and effects re-run when sources change.

ref versus reactive

APIBest forAccess patternGotcha
ref()Primitives, single values, template refscount.value in script; auto-unwrapped in templateRemember .value inside script
reactive()Plain objects with many fieldsstate.name direct property accessCannot reassign whole object without losing reactivity
computed()Derived read-only stateLike ref; lazy and cachedDo not mutate inside computed getter
shallowRef()Large external objectsOnly .value replacement triggers updatesInner property changes are not tracked

My default rule: start with ref(). Reach for reactive() when you have a stable object shape with many fields. On legal-tech forms with nested address blocks, reactive objects keep templates cleaner.

Vue 3 Reactivity Pipelineref()reactive()Proxy Trackertrack / triggercomputed()cached derivewatch()Template DOM
Vue 3 Composition API reactivity tracks ref and reactive reads, then updates computed values, watchers, and the DOM.

Computed and watch patterns

import { ref, computed, watch, watchEffect } from 'vue'

const price = ref(1000)
const qty = ref(2)
const discount = ref(0.1)

const subtotal = computed(() => price.value * qty.value)
const total = computed(() => subtotal.value * (1 - discount.value))

watch(qty, (newQty, oldQty) => {
  if (newQty > 99) qty.value = 99
})

watchEffect(() => {
  document.title = `Cart: Rs ${total.value.toFixed(0)}`
})

Use watch when you need old and new values or lazy execution. Use watchEffect when you want immediate tracking of every reactive read inside the callback. For currency formatting on Nepal-facing storefronts, computed formatters keep templates thin. Test edge cases with the JSON formatter tool when mocking API payloads locally.

Official reference: the Vue reactivity core API documentation lists every helper and overload.

How Do Composables Make Vue 3 Composition API Logic Reusable?

Composables are plain functions that call Composition API helpers. Name them useSomething. Return refs, reactive state, and methods. Import them into any component.

Example: useFetch composable

import { ref, shallowRef } from 'vue'

export function useFetch(url) {
  const data = shallowRef(null)
  const error = ref(null)
  const loading = ref(false)

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

  return { data, error, loading, execute }
}

Usage in a component:

<script setup>
import { onMounted } from 'vue'
import { useFetch } from '@/composables/useFetch'

const { data, error, loading, execute } = useFetch('/api/courts')

onMounted(execute)
</script>

That pattern mirrors REST consumption described in building RESTful APIs with Laravel. Keep fetch logic out of templates. Validate response shapes before binding.

For shared client state across many components, add Pinia. Compare approaches in the Pinia vs Vuex 4 for Vue 3 state management article. Pinia stores work well with Composition API syntax natively.

Composable Reuse Across ComponentsuseFetch()useAuth()SearchPage.vuelist + filtersProfilePage.vueuser menuCheckout.vuecart totals
Vue 3 composables let multiple components share fetch, auth, and formatting logic without mixins.

How Does Vue 3 Composition API Compare to the Options API?

Both APIs compile to the same runtime. Choose based on component complexity and team familiarity. Small presentational components can stay Options API. Feature-heavy widgets benefit from Composition API.

CriteriaComposition APIOptions API
Code organisationBy feature / composableBy option type
Logic reuseComposables (explicit imports)Mixins (implicit merge)
TypeScriptStrong inference with script setupNeeds component option typing helpers
Learning curveSteeper initiallyFamiliar to Vue 2 developers
Best fitComplex forms, dashboards, wizardsSimple display components

Incremental migration is valid. A Laravel 12 or 13 app can mount one Vue island per page. Legacy jQuery widgets can coexist while you rewrite hot paths. I have done this on Court Marriage In Nepal lead-capture flows without a full SPA rewrite.

Need SSR or file-based routing later? Read the Vue 3 SSR with Nuxt 3 complete guide. Nuxt 3 uses Composition API and script setup by default.

What Are Common Vue 3 Composition API Mistakes in Production?

Most bugs I debug are reactivity footguns, not template syntax errors. These five show up repeatedly.

  1. Destructuring reactive objects. const { name } = reactive(user) breaks reactivity. Use toRefs() or access properties on the reactive object directly.
  2. Mutating props. Props are read-only. Copy to local state with ref(props.initial) or computed getters.
  3. Forgetting onUnmounted cleanup. Clear intervals, abort fetch controllers, and remove listeners inside onUnmounted().
  4. Over-using watchEffect. Broad effects rerun often and hide dependencies. Prefer explicit watch for side effects tied to one source.
  5. Putting async directly on setup. setup() must not be async. Use an inner async function called from onMounted.
Composition API Decision TreeNew reactive state?primitiveUse ref()objectUse reactive()Shared logic?Shared logic?Extract composableExtract composable
Use this Vue 3 Composition API decision flow to pick ref, reactive, or a composable before writing state code.

Lifecycle hooks in Composition API

Every Options API hook has a Composition counterpart prefixed with on:

  • onBeforeMount / onMounted
  • onBeforeUpdate / onUpdated
  • onBeforeUnmount / onUnmounted
  • onErrorCaptured for child error boundaries

Call lifecycle hooks synchronously during setup. Registering them inside conditionals or after await breaks Vue's registration order.

TypeScript with script setup

<script setup lang="ts">
import { ref, computed } from 'vue'

interface CourtCase {
  id: number
  title: string
  status: 'open' | 'closed'
}

const cases = ref<CourtCase[]>([])

const openCases = computed(() =>
  cases.value.filter(c => c.status === 'open')
)
</script>

Generic components use <script setup lang="ts" generic="T"> in recent Vue 3 releases. See the Vue TypeScript Composition API guide for prop and emit typing with defineProps and defineEmits.

Testing composables

Test composables with Vitest or Jest by calling the function inside a wrapper component or using @vue/test-utils. Focus on returned state transitions, not implementation details. Align frontend tests with API contract checks from API contract testing with Pact when UI depends on strict response shapes.

For regex-heavy validators in forms, prototype patterns in the regex tester before embedding them in composables.

Integrating with Laravel backends

Typical stack on projects I deliver through web development in Nepal:

  • Laravel 12 or 13 serves Blade layouts and JSON endpoints.
  • Vite 8.x bundles Vue SFCs from resources/js.
  • Sanctum handles SPA cookie auth or token auth for mobile clients.
  • Axios or fetch composables centralise CSRF headers and 419 retry logic.

On Quick And Easy Nepalese Grocery, delivery-zone selectors used a composable backed by a Laravel geo endpoint. The same composable powered checkout and account pages.

Performance work belongs in both layers. Profile slow endpoints with Laravel tooling. Debounce search composables on the client. Our speed optimization service often finds N+1 queries behind sluggish Vue tables.

Date handling across BS and Gregorian calendars belongs in a dedicated utility. Pair UI work with patterns from JavaScript date handling with the Temporal API and the Nepali date converter for display formatting.

Key Takeaways

  • Use <script setup> and composables to group related logic; avoid scattering one feature across Options API blocks.
  • Default to ref() for primitives; use reactive() for stable object shapes; never destructure reactive without toRefs().
  • Extract useFetch, useAuth, and form helpers early so Laravel-mounted widgets stay DRY across pages.
  • Prefer explicit watch over broad watchEffect for side effects; always clean up in onUnmounted.
  • Composition API and Options API can coexist; migrate hot paths first on Blade + Vue hybrid apps.
  • Add Pinia when multiple routes share mutable client state; keep composables for local and fetch logic.

People Also Ask

Is the Composition API replacing the Options API?

No. Vue maintainers treat both as first-class. The Composition API solves organisation and reuse in complex components. Simple components can stay Options API indefinitely without penalty.

Do I need Pinia if I already use composables?

Composables handle local state and reusable logic inside components. Pinia adds a central store, devtools integration, and predictable cross-route state. Use composables for fetch wrappers; use Pinia for shared cart or session UI state.

Can I use Composition API inside Laravel Blade templates?

Yes. Compile Vue SFCs with Vite, register a root component on a DOM element, and pass initial data through Blade as JSON props. This hybrid pattern avoids a full SPA rewrite while modernising interactive sections.

What is the difference between ref and reactive?

ref wraps any value and requires .value in script. reactive wraps objects only and allows direct property access. For most cases, start with ref because it behaves consistently and works with primitives.

Ship Vue 3 Composition API Patterns With Confidence

This Vue 3 Composition API deep dive gives you the mental model: setup, tracked refs, composables, and lifecycle hooks wired for real backends. Start one component, extract a composable when logic repeats, and add Pinia only when state crosses routes. If you want help embedding Vue islands in Laravel, auditing reactivity bugs, or planning a migration from jQuery widgets, contact us or explore custom software development in Nepal. You can also review client work on the Mijar Law Associates portal and read more on the blog.

Frequently Asked Questions

The Composition API is a set of functions for building Vue 3 components alongside the Options API. You import helpers from Vue and use them inside setup() or script setup. It groups related logic by feature instead of splitting it across data, computed, methods, and watch blocks. Vue 3 shipped it as stable in 2021, and in 2026 most teams default to it for new components. Options API still works and Vue does not plan to remove it.

ref wraps any value and requires .value in script but auto-unwraps in templates. reactive wraps plain objects only and allows direct property access like state.name. Start with ref for primitives and single values; use reactive for stable object shapes with many fields. Destructuring reactive objects without toRefs breaks reactivity.

script setup is syntactic sugar that compiles to setup(). Top-level bindings are automatically exposed to the template without a manual return, which removes a common source of undefined-in-template bugs. Most new Composition API code uses script setup because it is shorter. Use explicit setup(props, context) only when integrating with non-standard build tools or when you must return a render function.

Install Vue 3 with Vite 8.x and npm 12 on Node.js 26 LTS. Run npm create vite@latest my-vue-app with the vue template, then npm install and npm run dev. For Laravel Blade apps, pair the Vite scaffold with patterns from embedding Vue islands inside server-rendered pages. Compile Vue SFCs from resources/js and register a root component on a DOM element.

Composables are plain functions named useSomething that call Composition API helpers and return refs, reactive state, and methods. Import them into any component. A useFetch composable can centralise loading, error, and data refs plus an execute function, keeping fetch logic out of templates. On booking dashboards, filters, pagination, and cart state each became composables that new pages imported without mixin side effects.

No. Vue maintainers treat both as first-class APIs. Composition API solves organisation and reuse in complex components. Simple presentational components can stay Options API indefinitely without penalty.

Both APIs compile to the same runtime. Composition API organises code by feature and composable, supports explicit reuse via imports, and gives strong TypeScript inference with script setup. Options API organises by option type, reuses logic via mixins with naming collision risks, and needs extra typing boilerplate. Choose Composition API for complex forms, dashboards, and wizards; keep Options API for simple display components. Incremental migration on Laravel apps is valid.

Composables handle local state and reusable logic inside components. Pinia adds a central store, devtools integration, and predictable cross-route state. Use composables for fetch wrappers; use Pinia when multiple routes share mutable client state like cart or session UI state.

Yes. Compile Vue SFCs with Vite, register a root component on a DOM element, and pass initial data through Blade as JSON props. This hybrid pattern avoids a full SPA rewrite while modernising interactive sections. Typical stack: Laravel 12 or 13 serves Blade layouts and JSON endpoints, Vite 8.x bundles Vue SFCs, and Sanctum handles SPA cookie auth or token auth for mobile clients.

Use watch when you need old and new values or lazy execution tied to a specific source. Use watchEffect when you want immediate tracking of every reactive read inside the callback. Over-using watchEffect is a common production mistake because broad effects rerun often and hide dependencies. Prefer explicit watch for side effects tied to one source, such as capping quantity or updating document title from cart totals.

computed() creates derived read-only state that is lazy and cached. Dependent values re-run only when tracked ref or reactive sources change. Use it for subtotals, discounts, filtered lists, and currency formatters that keep templates thin. Do not mutate state inside a computed getter. On Nepal-facing storefronts, computed formatters handle currency display without cluttering the template.

Five bugs recur: destructuring reactive objects without toRefs breaks reactivity; mutating read-only props instead of copying to local state; forgetting onUnmounted cleanup for intervals, fetch abort controllers, and listeners; over-using watchEffect when explicit watch is clearer; and making setup async directly instead of calling an inner async function from onMounted. Most production bugs are reactivity footguns, not template syntax errors.

No. setup() must not be async. Use an inner async function and call it from onMounted or another lifecycle hook. Lifecycle hooks must be registered synchronously during setup; registering them inside conditionals or after await breaks Vue registration order.

Every Options API hook has a Composition counterpart prefixed with on, including onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, and onErrorCaptured for child error boundaries. Call lifecycle hooks synchronously during setup. Registering them inside conditionals or after await breaks Vue registration order. Use onUnmounted to clear intervals, abort fetch controllers, and remove event listeners.

Test composables with Vitest or Jest by calling the function inside a wrapper component or using @vue/test-utils. Focus on returned state transitions such as loading, error, and data changes, not implementation details. When UI depends on strict API response shapes, align frontend tests with API contract checks. For regex-heavy form validators, prototype patterns in a regex tester before embedding them in composables.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: