Fix: inject() Can Only Be Used Inside setup() in Vue.js

Error message:
inject() can only be used inside setup() or functional components.
Composition API 2025-01-25

What Causes This Error?

This error occurs when you call inject() outside of a component’s setup() function or outside a functional component. Vue’s dependency injection system requires an active component instance to work properly.

The Problem

// ❌ Called outside setup
import { inject } from 'vue'

const theme = inject('theme') // Error!

export default {
  setup() {
    // Too late - inject was called above
  }
}
// ❌ Called in async callback
export default {
  setup() {
    setTimeout(() => {
      const theme = inject('theme') // Error!
    }, 1000)
  }
}

The Fix

Call inject() Inside setup()

import { inject } from 'vue'

export default {
  setup() {
    // ✅ Called synchronously inside setup
    const theme = inject('theme')

    return { theme }
  }
}

With Script Setup

<script setup>
import { inject } from 'vue'

// ✅ Top-level in script setup works
const theme = inject('theme')
const user = inject('user')
</script>

Store inject() Result for Later Use

import { inject, ref } from 'vue'

export default {
  setup() {
    // ✅ Get the injected value synchronously
    const api = inject('api')
    const data = ref(null)

    // Use the injected value later
    async function fetchData() {
      data.value = await api.getData() // ✅ Using stored reference
    }

    return { data, fetchData }
  }
}

Common Scenarios

Creating Composables

// ❌ Wrong - inject outside setup context
export function useTheme() {
  const theme = inject('theme') // Error if called outside setup!
  return theme
}

// ✅ Correct - document that it must be called in setup
export function useTheme() {
  // This composable must be called within setup()
  const theme = inject('theme')

  if (!theme) {
    throw new Error('useTheme requires ThemeProvider')
  }

  return theme
}

// Usage in component
export default {
  setup() {
    const theme = useTheme() // ✅ Called during setup
    return { theme }
  }
}

In Event Handlers

// ❌ Wrong - inject in handler
export default {
  setup() {
    function handleClick() {
      const api = inject('api') // Error!
      api.doSomething()
    }
    return { handleClick }
  }
}

// ✅ Correct - inject in setup, use in handler
export default {
  setup() {
    const api = inject('api')

    function handleClick() {
      api.doSomething() // ✅ Using already-injected value
    }

    return { handleClick }
  }
}

With Async Operations

// ❌ Wrong - inject after await
export default {
  async setup() {
    await someAsyncOperation()
    const theme = inject('theme') // Error!
  }
}

// ✅ Correct - inject before await
export default {
  async setup() {
    const theme = inject('theme') // ✅ Before any await

    await someAsyncOperation()

    // theme is still accessible
    return { theme }
  }
}

How provide/inject Works

// Parent component
import { provide } from 'vue'

export default {
  setup() {
    const theme = reactive({
      color: 'dark',
      toggle() { this.color = this.color === 'dark' ? 'light' : 'dark' }
    })

    provide('theme', theme)
  }
}

// Child/descendant component
import { inject } from 'vue'

export default {
  setup() {
    // ✅ Must be called synchronously in setup
    const theme = inject('theme')

    // Can provide default value
    const config = inject('config', { debug: false })

    return { theme, config }
  }
}

Quick Checklist

  • Call inject() synchronously in setup() or <script setup>
  • Call inject() before any await statements
  • Store injected values in variables for later use
  • Don’t call inject() in event handlers or callbacks
  • Document that composables using inject() must be called in setup
  • Provide default values for optional injections