Het probleem: props drilling

Stel je hebt een gebruiker-object in App.vue dat je nodig hebt in een diep genest UserBadge component:

App.vue (user hier)
└─ Layout.vue
   └─ Sidebar.vue
      └─ ProfileMenu.vue
         └─ UserBadge.vue  ← user hier nodig

Zonder hulp moet je user als prop doorgeven via elk tussencomponent. Dat heet props drilling — vermoeiend en foutgevoelig.

Met provide / inject kan een parent data direct naar een (eventueel diep geneste) child sturen, zonder tussencomponenten.

provide() & inject()

In de parent: provide

<!-- src/App.vue -->
<script setup>
import { provide } from 'vue'

const user = { name: 'Anna', role: 'admin' }

provide('user', user)   // sleutel + waarde
</script>

In het (diep geneste) child: inject

<!-- src/components/UserBadge.vue (kan diep genest zitten) -->
<script setup>
import { inject } from 'vue'

const user = inject('user')
</script>

<template>
  <span>{{ user.name }} ({{ user.role }})</span>
</template>

Wat is er gebeurd? De parent zegt: "hier is een waarde onder de naam 'user'". Elke (sub-)child kan die opvragen met inject('user') — ongeacht hoe diep hij ligt.

Default value voor inject

Als de parent niets provide't, geef je een fallback mee:

const user = inject('user', { name: 'Gast' })   // default

Reactivity bewaren — provide met een ref

Wil je dat de child meegroeit met wijzigingen in de parent? Provide een ref, niet een gewone waarde.

<!-- src/App.vue -->
<script setup>
import { ref, provide } from 'vue'

const user = ref({ name: 'Anna', role: 'admin' })

provide('user', user)   // ref doorgeven

const promote = () => {
  user.value.role = 'super-admin'
  // Alle componenten die 'user' injecten, zien de wijziging direct!
}
</script>
<!-- src/components/UserBadge.vue -->
<script setup>
import { inject } from 'vue'

const user = inject('user')
</script>

<template>
  <span>{{ user.name }} ({{ user.role }})</span>
  <!-- updatet automatisch als parent user.value muteert -->
</template>

Let op: Provide je een gewone const user = {...} (geen ref)? Dan is het niet reactive. Wijzigingen in de parent komen niet door in de children.

Compleet voorbeeld — dark mode toggle

De klassieke use case: een dark/light theme dat overal in de app beschikbaar moet zijn.

App.vue — provide

<script setup>
import { ref, provide } from 'vue'
import Layout from './components/Layout.vue'

const isDark = ref(false)

const toggleTheme = () => {
  isDark.value = !isDark.value
}

// Provide de state EN de toggle-functie
provide('theme', { isDark, toggleTheme })
</script>

<template>
  <div :class="{ dark: isDark }">
    <Layout />
  </div>
</template>

ThemeToggle.vue — diep genest, gebruikt het

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

const { isDark, toggleTheme } = inject('theme')
</script>

<template>
  <button @click="toggleTheme">
    {{ isDark ? '☀️ Light mode' : '🌙 Dark mode' }}
  </button>
</template>

Wat heb je nu? Vanuit elk component (hoe diep ook) kan je het thema lezen en wisselen. Geen props doorgeven, geen events naar boven sturen.

Provide/inject of Pinia?

Allebei werken voor "state delen tussen componenten." Wanneer kies je wat?

Provide/Inject Pinia
Bereik Alleen child-tree Globaal (hele app)
Setup Geen install nodig npm install pinia
Devtools Beperkt Volledig zichtbaar
Beste voor Theme, layout-config, lokale tree Auth, user data, cart, alles globaal

Vuistregel:

  • Iets dat in je hele app speelt (ingelogde gebruiker, winkelmandje) → Pinia
  • Iets dat alleen binnen één sectie speelt (layout-config, modal-context) → provide/inject

Belangrijke Regels

  • provide('key', value) in de parent
  • inject('key') in het child — werkt op elk diep level
  • Voor reactive data: provide een ref, geen gewone waarde
  • Key kan een string zijn — kies een unieke, beschrijvende naam
  • Voor globale state: gebruik Pinia, niet provide/inject

Veelgemaakte Fouten

Fout — gewone waarde provide'n, verwachten dat het reactive is:

// Parent
let user = { name: 'Anna' }
provide('user', user)

user.name = 'Bram'   // ❌ child ziet dit niet

Goed — provide een ref:

const user = ref({ name: 'Anna' })
provide('user', user)

user.value.name = 'Bram'   // ✅ child updatet

Fout — key mismatch:

// Parent
provide('currentUser', user)

// Child
const user = inject('user')   // ❌ andere naam, krijgt undefined

Goed:

const user = inject('currentUser')   // ✅ zelfde key