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/define-expose-before-await.md
1---2title: defineExpose Must Be Called Before Any Await3impact: HIGH4impactDescription: Properties exposed after await are inaccessible to parent component refs5type: gotcha6tags: [vue3, script-setup, defineExpose, async, component-refs]7---89# defineExpose Must Be Called Before Any Await1011**Impact: HIGH** - In `<script setup>`, if you call `defineExpose()` after an `await` statement, the exposed properties will NOT be accessible to parent components using template refs. This is a subtle async timing issue that causes silent failures.1213The compiler transforms top-level await, and code after await runs in a different execution context where defineExpose cannot properly register with the component instance.1415## Task Checklist1617- [ ] Always call defineExpose() at the top of script setup, before any await18- [ ] If async data is needed in exposed methods, fetch it separately19- [ ] Structure code so expose declarations come first20- [ ] Test parent ref access when using async setup2122**Incorrect:**23```vue24<!-- ChildComponent.vue -->25<script setup>26import { ref } from 'vue'2728const data = ref(null)29const count = ref(0)3031function increment() {32count.value++33}3435// WRONG: await before defineExpose36const response = await fetch('/api/data')37data.value = await response.json()3839// BROKEN: This won't work - called after await!40defineExpose({41count,42increment,43data44})45</script>4647<template>48<div>{{ data }}</div>49</template>50```5152```vue53<!-- ParentComponent.vue -->54<script setup>55import { ref, onMounted } from 'vue'56import ChildComponent from './ChildComponent.vue'5758const childRef = ref(null)5960onMounted(() => {61// FAILS: All exposed properties are undefined!62console.log(childRef.value.count) // undefined63childRef.value.increment() // TypeError64})65</script>6667<template>68<Suspense>69<ChildComponent ref="childRef" />70</Suspense>71</template>72```7374**Correct:**75```vue76<!-- ChildComponent.vue -->77<script setup>78import { ref } from 'vue'7980const data = ref(null)81const count = ref(0)8283function increment() {84count.value++85}8687// CORRECT: defineExpose BEFORE any await88defineExpose({89count,90increment,91data92})9394// Now safe to use await95const response = await fetch('/api/data')96data.value = await response.json()97</script>9899<template>100<div>{{ data }}</div>101</template>102```103104```vue105<!-- Alternative: Separate async logic from expose -->106<script setup>107import { ref, onMounted } from 'vue'108109const data = ref(null)110const loading = ref(true)111112function getData() {113return data.value114}115116async function refreshData() {117loading.value = true118const response = await fetch('/api/data')119data.value = await response.json()120loading.value = false121}122123// CORRECT: No await at top level - defineExpose always works124defineExpose({125data,126getData,127refreshData,128loading129})130131// Trigger async load in lifecycle hook instead132onMounted(() => {133refreshData()134})135</script>136137<template>138<div v-if="loading">Loading...</div>139<div v-else>{{ data }}</div>140</template>141```142143```vue144<!-- If you must use top-level await, define expose first -->145<script setup>146import { ref } from 'vue'147148const user = ref(null)149const posts = ref([])150151// CORRECT: All expose calls come first152defineExpose({153user,154posts,155refresh: () => loadData()156})157158// Now safe to await159async function loadData() {160const [userRes, postsRes] = await Promise.all([161fetch('/api/user'),162fetch('/api/posts')163])164user.value = await userRes.json()165posts.value = await postsRes.json()166}167168// Top-level await after defineExpose is safe169await loadData()170</script>171```172173## Why This Happens174175Vue's compiler transforms `<script setup>` with top-level await into an async setup function. The component instance context is only available synchronously before the first await. After await, the execution resumes outside that context, making defineExpose ineffective.176177```javascript178// What the compiler roughly generates:179async setup() {180const count = ref(0)181182// Context available here183await fetch(...) // Suspends execution184185// Context lost after resuming186defineExpose({ count }) // Too late!187}188```189190## Reference191- [Vue.js Script Setup - defineExpose](https://vuejs.org/api/sfc-script-setup.html#defineexpose)192- [Vue.js Async Components](https://vuejs.org/guide/components/async.html)193