DEV Community

Cover image for Code Refactoring: Avoid Prop Drilling in Vue with Provide/Inject

Code Refactoring: Avoid Prop Drilling in Vue with Provide/Inject

Introduction

Prop drilling is a common concept in frontend development, especially in component based frameworks like Vue.js. It refers to the practice of passing data from a parent component down to its nested child components through props. While this is a straightforward way to share data, it can lead to certain challenges, especially in large and deeply nested component trees.

Challenges

  • Boilerplate Code: Prop drilling often leads to redundant code as props need to be passed through multiple layers of components.
  • Tight Coupling: Components become tightly coupled to the data they receive via props, reducing reusability and making maintenance more challenging.
  • Scalability Issues: As component trees grow larger and deeper, managing prop passing becomes increasingly complex.
  • Debugging Complexity: Tracking data flow through multiple levels of components can complicate debugging efforts, especially when props are mutated or modified along the way.

Scenario

Imagine an application where we have a ProductList component that displays a list of products. Each product has a ProductDetail component that needs to display detailed information, and within ProductDetail, there is a ProductReview component showing customer reviews. Let's see how to pass data down from the ProductList to the ProductReview component.

ProductList.vue

<template>
  <ul>
    <li v-for="product in products" :key="product.id">
      <ProductDetail :product="product" />
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';
import ProductDetail from './ProductDetail.vue';

const products = ref([
  { id: 1, name: 'Product A', reviews: ['Great product!', 'Loved it!'] },
  { id: 2, name: 'Product B', reviews: ['Not bad.', 'Could be better.'] },
]);
</script>
Enter fullscreen mode Exit fullscreen mode

ProductDetail.vue

<template>
  <div>
    <h2>{{ product.name }}</h2>
    <ProductReview :reviews="product.reviews" />
  </div>
</template>

<script setup>
import { defineProps } from 'vue';
import ProductReview from './ProductReview.vue';

const props = defineProps({
  product: {
    type: Object,
    required: true,
  },
});
</script>
Enter fullscreen mode Exit fullscreen mode

ProductReview.vue

<template>
  <ul>
    <li v-for="review in reviews" :key="review">{{ review }}</li>
  </ul>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  reviews: {
    type: Array,
    required: true,
  },
});
</script>
Enter fullscreen mode Exit fullscreen mode

Solutions

To avoid prop drilling we can make use of state management library like Vuex and Pinia, but for this article I will demonstrate of using Vue provide and inject. Let's refactor our components.

ProductList.vue

<template>
  <ul>
    <li v-for="product in products" :key="product.id">
      <ProductDetail :product="product" />
    </li>
  </ul>
</template>

<script setup>
import { ref, provide } from 'vue';
import ProductDetail from './ProductDetail.vue';

const products = ref([
  { id: 1, name: 'Product A', reviews: ['Great product!', 'Loved it!'] },
  { id: 2, name: 'Product B', reviews: ['Not bad.', 'Could be better.'] },
]);

provide('products', products);
</script>
Enter fullscreen mode Exit fullscreen mode

ProductDetail.vue

<template>
  <div>
    <h2>{{ product.name }}</h2>
    <ProductReview :product-id="product.id" />
  </div>
</template>

<script setup>
import { defineProps } from 'vue';
import ProductReview from './ProductReview.vue';

const props = defineProps({
  product: {
    type: Object,
    required: true,
  },
});
</script>
Enter fullscreen mode Exit fullscreen mode

ProductReview.vue

<template>
  <ul>
    <li v-for="review in productReviews" :key="review">{{ review }}</li>
  </ul>
</template>

<script setup>
import { defineProps, inject, computed } from 'vue';

const props = defineProps({
  'product-id': {
    type: Number,
    required: true,
  },
});

const products = inject('products');
const productReviews = computed(() => {
  const product = products.value.find(p => p.id === props['product-id']);
  return product ? product.reviews : [];
});
</script>
Enter fullscreen mode Exit fullscreen mode

By using provide/inject we eliminate the needs to pass down our props hence we can keep our component hierarchy clean and maintainable, improving the overall quality and scalability of your application.

Conclusion

Prop drilling in Vue can lead to redundant code, tight coupling, scalability issues, and challenging debugging. Using Vue's provide/inject can solve these problems. This approach bypasses intermediate components, resulting in smoother data flow and cleaner code, making our code more maintainable and scalable.

Top comments (0)