How to avoid and fix prop drilling in Vue.js

I've been using Vue.js for a while now but there are still some patterns and things I discover. Recently I did a course at Vueschool.io on the topic of common mistakes in Vue.js and how to fix them. One concept they explained was Prop drilling.

In this blog I'll show you what prop drilling is and how to fix it.

What is prop drilling?

Say you have a several components which are nested: You have an App which has a ChildComponent and a GrandComponent.

App
--ChildComponent
----GrandChildComponent

If you want to use a variable from the App in GrandChildComponent, you need to pass down a prop to the ChildComponent which in turn passes it down to the GrandChildComponent it gets messy pretty quickly, especially if it is even further down the hierarchy. You might forget to pass the prop or you accidentally modify the prop as you pass it along.

So how do we fix this?

Provide/inject

One solution is to use provide/inject into your components.

Example

in App.vue you do the following:

<script setup>
import { provide, ref } from 'vue'

const currentWriter = ref({
  name: 'John Doe',
  storyCount: 5,
});

provide('currentWriter', currentWriter);
</script>

In GrandChildComponent.vue

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

const currentWriter = inject('currentWriter');
</script>

Easy! 👍

You can also add a function that modifies the value that is being passed down. It is recommended to keep the function inside of the 'Provider'. This makes it easier to maintain because it keeps the logic in 1 place.

<script setup>
import { provide, ref } from 'vue'

const currentWriter = ref({
  name: 'John Doe',
  storyCount: 5,
});

function addStory() {
  currentWriter.value.storyCount++;
}

provide('currentWriter', {
  currentWriter,
  addStory
});
</script>

In GrandChildComponent.vue

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

const {currentWriter, addStory} = inject('currentWriter');
</script>

<template>
  {{ currentWriter.storyCount }}
  <button @click="addStory">Add story</button>
</template>

It's quite easy to use and keeps the code clean. If you'd like to know more, you can view the the VueJS documentation. There are some other approaches to fix this issue like Composables or state management. You can check out the documentation which provides some very clear examples!