The Problem

When you call a composable function (useSomething) that contains onMounted or other lifecycle hooks, those hooks must be registered during the component's setup phase. If the composable is called at the wrong time, the lifecycle hooks silently fail.

Symptoms

  • onMounted inside a composable never fires
  • DOM queries in composable return null
  • Data fetching composable does not auto-fetch on mount
  • Works when code is in the component but not when extracted to a composable

Real Error Scenario

```javascript // useWindowSize.js export function useWindowSize() { const width = ref(window.innerWidth);

onMounted(() => { window.addEventListener('resize', handleResize); // Never fires! });

onUnmounted(() => { window.removeEventListener('resize', handleResize); });

return { width }; }

// Component.vue (WRONG usage) <script setup> let windowSize;

// Called inside a function, not at top level of setup function initWindowTracking() { windowSize = useWindowSize(); // onMounted won't register! }

// Never called, or called too late </script> ```

The Rule: Composables Must Be Called at Setup Top Level

javascript
// CORRECT: Called synchronously during setup
<script setup>
const { width } = useWindowSize(); // onMounted registers correctly
</script>

How to Fix It

Fix 1: Call Composable at Top Level

vue
<script setup>
// ALWAYS call composables at the top level of setup
const { width } = useWindowSize();
const { data } = useFetch('/api/users');
const { user } = useAuth();
</script>

Fix 2: Conditional Composable Setup

```javascript // WRONG: Conditional hook call if (isAdmin) { const { permissions } = usePermissions(); }

// CORRECT: Always call, conditionally use const { permissions } = usePermissions(); const canEdit = computed(() => isAdmin.value && permissions.value.canEdit); ```

Fix 3: Composable with Manual Trigger

```javascript // When you need control over when the composable initializes export function useDataFetcher(url) { const data = ref(null); const loading = ref(false);

async function fetch() { loading.value = true; data.value = await fetch(url).then(r => r.json()); loading.value = false; }

// Don't auto-fetch in onMounted, let caller decide return { data, loading, fetch }; }

// Usage: const { data, loading, fetch } = useDataFetcher('/api/users'); fetch(); // Manual trigger ```

Fix 4: Dynamic Composable Registration

```javascript export function useConditionalFeature(featureFlag) { const enabled = ref(false);

onMounted(() => { enabled.value = checkFeatureFlag(featureFlag); });

return { enabled }; }

// Always call the composable const { enabled: showDashboard } = useConditionalFeature('dashboard-v2'); ```