Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/composable-call-location-restrictions.md
1---2title: Call Composables Only in Setup Context Synchronously3impact: HIGH4impactDescription: Composables called outside setup context or asynchronously fail to register lifecycle hooks and may cause memory leaks5type: gotcha6tags: [vue3, composables, composition-api, setup, async, lifecycle]7---89# Call Composables Only in Setup Context Synchronously1011**Impact: HIGH** - Composables must be called synchronously within `<script setup>`, the `setup()` function, or lifecycle hooks. Calling composables asynchronously (after await), in callbacks, or outside component context prevents Vue from associating lifecycle hooks with the component instance, causing silent failures.1213This is critical because composables often register `onMounted` and `onUnmounted` hooks internally. If called in the wrong context, these hooks are never registered, leading to uninitialized state or memory leaks.1415## Task Checklist1617- [ ] Call all composables at the top level of `<script setup>` or `setup()`18- [ ] Never call composables inside async callbacks, setTimeout, or Promise.then19- [ ] Never call composables conditionally (if/else) - call unconditionally and handle the condition inside20- [ ] Never call composables inside loops - restructure to call once with array data21- [ ] Exception: Composables CAN be called in lifecycle hooks like `onMounted`2223**Incorrect:**24```vue25<script setup>26import { useFetch } from './composables/useFetch'27import { useAuth } from './composables/useAuth'2829// WRONG: Composable called after await30const config = await loadConfig()31const { data } = useFetch(config.apiUrl) // Lifecycle hooks won't register!3233// WRONG: Composable called conditionally34if (someCondition) {35const { user } = useAuth() // Inconsistent hook registration!36}3738// WRONG: Composable called in callback39setTimeout(() => {40const { data } = useFetch('/api/delayed') // No component context!41}, 1000)4243// WRONG: Composable called in loop44for (const url of urls) {45const { data } = useFetch(url) // Creates multiple instances incorrectly46}47</script>48```4950**Correct:**51```vue52<script setup>53import { ref, onMounted } from 'vue'54import { useFetch } from './composables/useFetch'55import { useAuth } from './composables/useAuth'5657// CORRECT: Call composables synchronously at top level58const { user, isAuthenticated } = useAuth()59const apiUrl = ref('/api/default')60const { data, execute } = useFetch(apiUrl)6162// Handle async config loading differently63onMounted(async () => {64const config = await loadConfig()65apiUrl.value = config.apiUrl // Update the ref, composable reacts66})6768// CORRECT: Handle condition inside, not outside69const showUserData = computed(() => isAuthenticated.value && someCondition)7071// CORRECT: For multiple URLs, use a different pattern72const urls = ref(['/api/a', '/api/b', '/api/c'])73const results = ref([])7475// Either fetch in onMounted or use a composable designed for arrays76onMounted(async () => {77results.value = await Promise.all(urls.value.map(url => fetch(url)))78})79</script>80```8182## Exception: Calling in Lifecycle Hooks8384Composables CAN be called inside lifecycle hooks because Vue maintains the component context:8586```vue87<script setup>88import { onMounted } from 'vue'89import { useEventListener } from '@vueuse/core'9091// CORRECT: Called in lifecycle hook - component context is available92onMounted(() => {93// This works because we're still in the component's execution context94useEventListener(document, 'visibilitychange', handleVisibility)95})96</script>97```9899## Special Case: Async Setup in `<script setup>`100101Top-level await in `<script setup>` is special - Vue's compiler automatically preserves context:102103```vue104<script setup>105import { useFetch } from './composables/useFetch'106107// CORRECT: Top-level await in <script setup> preserves context108// Vue compiler handles this specially109const config = await loadConfig()110const { data } = useFetch(config.apiUrl) // This works!111112// But nested awaits still break context:113async function initLater() {114await delay(1000)115const { data } = useFetch('/api/late') // WRONG: This won't work!116}117</script>118```119120## Why This Matters121122When you call a composable, Vue needs to know which component instance to associate it with. This association happens through an internal "current instance" that's only set during synchronous setup execution.123124```javascript125// Inside a composable126export function useFetch(url) {127const data = ref(null)128129// These need the current component instance!130onMounted(() => { /* ... */ })131onUnmounted(() => { /* cleanup */ })132133// If called outside setup context, Vue can't find the instance134// and these hooks are silently ignored135return { data }136}137```138139## Reference140- [Vue.js Composables - Usage Restrictions](https://vuejs.org/guide/reusability/composables.html#usage-restrictions)141- [Vue.js Composition API - Setup Context](https://vuejs.org/api/composition-api-setup.html)142