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 insetup()or<script setup> - Call
inject()before anyawaitstatements - 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