
September 08, 2026
11 min read
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.
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
| API | Best for | Access pattern | Gotcha |
|---|---|---|---|
ref() | Primitives, single values, template refs | count.value in script; auto-unwrapped in template | Remember .value inside script |
reactive() | Plain objects with many fields | state.name direct property access | Cannot reassign whole object without losing reactivity |
computed() | Derived read-only state | Like ref; lazy and cached | Do not mutate inside computed getter |
shallowRef() | Large external objects | Only .value replacement triggers updates | Inner 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.
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.
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.
| Criteria | Composition API | Options API |
|---|---|---|
| Code organisation | By feature / composable | By option type |
| Logic reuse | Composables (explicit imports) | Mixins (implicit merge) |
| TypeScript | Strong inference with script setup | Needs component option typing helpers |
| Learning curve | Steeper initially | Familiar to Vue 2 developers |
| Best fit | Complex forms, dashboards, wizards | Simple 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.
- Destructuring reactive objects.
const { name } = reactive(user)breaks reactivity. UsetoRefs()or access properties on the reactive object directly. - Mutating props. Props are read-only. Copy to local state with
ref(props.initial)or computed getters. - Forgetting onUnmounted cleanup. Clear intervals, abort fetch controllers, and remove listeners inside
onUnmounted(). - Over-using watchEffect. Broad effects rerun often and hide dependencies. Prefer explicit
watchfor side effects tied to one source. - Putting async directly on setup.
setup()must not be async. Use an inner async function called fromonMounted.
Lifecycle hooks in Composition API
Every Options API hook has a Composition counterpart prefixed with on:
onBeforeMount/onMountedonBeforeUpdate/onUpdatedonBeforeUnmount/onUnmountedonErrorCapturedfor 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; usereactive()for stable object shapes; never destructure reactive withouttoRefs(). - Extract
useFetch,useAuth, and form helpers early so Laravel-mounted widgets stay DRY across pages. - Prefer explicit
watchover broadwatchEffectfor side effects; always clean up inonUnmounted. - 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
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.

