Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Passing data deep: provide and inject

Sometimes data is needed by a component several levels down: App.vue → TodoList.vue → TodoItem.vue. Passing it through the props of every intermediate component is tedious. The provide function makes a value available to all descendants, and inject takes it out where it is needed.

Providing a value

In src/App.vue call provide with a key and a value:

<!-- src/App.vue -->
<script setup>
import { ref, provide } from 'vue'
import TodoList from './components/TodoList.vue'
 
const theme = ref('light')
provide('theme', theme)
</script>
 
<template>
  <button @click="theme = theme === 'light' ? 'dark' : 'light'">
    Switch theme
  </button>
  <TodoList />
</template>

The key 'theme' is the name descendants use to find the value. The ref itself is passed, so descendants see its changes.

Injecting a value

In any nested component, for example src/components/TodoItem.vue, call inject with the same key:

<!-- src/components/TodoItem.vue -->
<script setup>
import { inject } from 'vue'
 
defineProps({ todo: Object })
 
const theme = inject('theme')
</script>
 
<template>
  <li :class="theme">{{ todo.text }}</li>
</template>
 
<style scoped>
.dark {
  background: #222;
  color: #eee;
}
</style>

The "Switch theme" button in App.vue switches the background of every task to dark and back. TodoList knows nothing about the theme and does not pass it on.

A default value

If no ancestor called provide with this key, inject returns undefined and Vue prints a warning. The second argument sets a fallback value:

<script setup>
import { inject } from 'vue'
 
const theme = inject('theme', 'light')
</script>

This way the component also works outside App.vue, for example in a test.

Changes only by the owner

To keep descendants from changing the shared value at will, pass a function for changing it along with the value:

<!-- src/App.vue -->
<script setup>
import { ref, provide, readonly } from 'vue'
 
const theme = ref('light')
 
function toggleTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}
 
provide('theme', { theme: readonly(theme), toggleTheme })
</script>
<!-- src/components/ThemeButton.vue -->
<script setup>
import { inject } from 'vue'
 
const { theme, toggleTheme } = inject('theme')
</script>
 
<template>
  <button @click="toggleTheme">Theme: {{ theme }}</button>
</template>

readonly returns a version of the ref that cannot be changed. The theme can be changed only through toggleTheme — the change logic stays in one place.

When to use what

SituationApproach
A direct child needs the dataprops
One value for a whole branch of the tree: theme, languageprovide and inject
Data shared by the whole app: user, cartA Pinia store (see the Pinia chapter)

Common mistake

Passing a value instead of the ref to provide: provide('theme', theme.value). Descendants get the string 'light' as it was at the time of the call and never learn about the theme switch. Pass the ref itself — that keeps the link.

Thanks for reading!

Donate to the site — we work hard for you.