Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 07 Beginner

Props and Component Communication

Updated: May 18, 2026
5 min read

# CHAPTER 7

Props and Component Communication

1. Chapter Introduction

Components need to talk to each other. Vue uses a "one-way data flow" architecture: data flows DOWN via props, events flow UP via $emit. This predictable pattern makes debugging easy — you always know where data comes from.

2. Learning Objectives

  • Pass data from parent to child with defineProps.
  • Emit events from child to parent with defineEmits.
  • Validate props with type checking and required flags.
  • Use v-model on custom components.

3. Props (Parent → Child)

vue
12345678910111213141516171819202122232425262728293031323334353637383940414243
<!-- ProductCard.vue (Child) -->
<script setup>
// defineProps declares what the parent can pass in
const props = defineProps({
  name: {
    type: String,
    required: true
  },
  price: {
    type: Number,
    required: true
  },
  category: {
    type: String,
    default: &#039;General'
  },
  inStock: {
    type: Boolean,
    default: true
  },
  tags: {
    type: Array,
    default: () => []  // Array/Object defaults must be factory functions
  }
})

// Access props in script
console.log(props.name)
console.log(props.price)
</script>

<template>
  <!-- Access props directly in template (no props. prefix needed) -->
  <div class="product-card" :class="{ &#039;out-of-stock': !inStock }">
    <h3>{{ name }}</h3>
    <span class="category">{{ category }}</span>
    <p class="price">${{ price.toFixed(2) }}</p>
    <div class="tags">
      <span v-for="tag in tags" :key="tag" class="tag">{{ tag }}</span>
    </div>
    <span v-if="!inStock" class="oos-badge">Out of Stock</span>
  </div>
</template>
vue
123456789101112131415161718192021
<!-- App.vue (Parent) -->
<script setup>
import ProductCard from &#039;./components/ProductCard.vue'
</script>

<template>
  <!-- Passing static values -->
  <ProductCard name="MacBook Pro" :price="1999.99" />

  <!-- Passing dynamic values (v-bind shorthand) -->
  <ProductCard
    :name="product.name"
    :price="product.price"
    :category="product.category"
    :in-stock="product.stock > 0"
    :tags="product.tags"
  />

  <!-- Spread all properties of an object as props -->
  <ProductCard v-bind="product" />
</template>

4. Emitting Events (Child → Parent)

vue
1234567891011121314151617181920212223242526272829303132333435
<!-- QuantitySelector.vue (Child) -->
<script setup>
import { ref } from &#039;vue'

// defineEmits declares what events this component can emit
const emit = defineEmits([&#039;increment', 'decrement', 'change', 'reset'])

// With validation (Vue 3.3+)
// const emit = defineEmits({
//   change: (val) => typeof val === 'number',
// })

const quantity = ref(1)

function increment() {
  quantity.value++
  emit(&#039;increment', quantity.value)    // Emit event with payload
  emit(&#039;change', quantity.value)       // Also emit generic change
}

function decrement() {
  if (quantity.value <= 1) return
  quantity.value--
  emit(&#039;decrement', quantity.value)
  emit(&#039;change', quantity.value)
}
</script>

<template>
  <div class="qty-selector">
    <button @click="decrement">−</button>
    <span>{{ quantity }}</span>
    <button @click="increment">+</button>
  </div>
</template>
vue
12345678910111213141516171819202122232425262728
<!-- CartItem.vue (Parent) -->
<script setup>
import QuantitySelector from &#039;./QuantitySelector.vue'
import { ref } from &#039;vue'

const quantity = ref(1)
const price = ref(29.99)

function handleQuantityChange(newQty) {
  quantity.value = newQty
  console.log(&#039;Total:', newQty * price.value)
}
</script>

<template>
  <div class="cart-item">
    <span>Vue Course — ${{ price }}</span>

    <!-- Listen for emitted events with @ -->
    <QuantitySelector
      @increment="(q) => console.log(&#039;Up:', q)"
      @decrement="(q) => console.log(&#039;Down:', q)"
      @change="handleQuantityChange"
    />

    <span>Total: ${{ (quantity * price).toFixed(2) }}</span>
  </div>
</template>

5. Custom v-model on Components

vue
1234567891011121314
<!-- BaseInput.vue — Custom component supporting v-model -->
<script setup>
// For v-model support:
defineProps({ modelValue: String })
const emit = defineEmits([&#039;update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="emit(&#039;update:modelValue', $event.target.value)"
    class="base-input"
  />
</template>
vue
1234567891011
<!-- Parent using v-model on custom component -->
<script setup>
import { ref } from &#039;vue'
import BaseInput from &#039;./BaseInput.vue'
const email = ref(&#039;')
</script>
<template>
  <!-- v-model expands to: :modelValue="email" @update:modelValue="email = $event" -->
  <BaseInput v-model="email" />
  <p>Email: {{ email }}</p>
</template>

6. Prop Validation Summary

javascript
123456789101112131415161718192021222324
defineProps({
  // Type only
  title: String,
  count: Number,

  // Multiple types
  value: [String, Number],

  // Required
  id: { type: Number, required: true },

  // With default
  status: { type: String, default: &#039;active&#039; },

  // Array default (must be factory function)
  items: { type: Array, default: () => [] },

  // Custom validator
  size: {
    type: String,
    default: &#039;md&#039;,
    validator: (value) => [&#039;sm&#039;, &#039;md&#039;, &#039;lg&#039;, &#039;xl&#039;].includes(value)
  }
})

7. Common Mistakes

  • Mutating props directly: props.name = 'Bob' is wrong and causes Vue warnings. Props are read-only. Emit an event to ask the parent to change data.
  • Wrong prop name casing: In template, props passed as :user-name must be declared as userName (camelCase) in defineProps. Vue auto-converts kebab-case to camelCase.

8. MCQs

Question 1

How does data flow in Vue components?

Question 2

defineProps is used in?

Question 3

Can you mutate a prop directly?

Question 4

How do you emit an event from a child?

Question 5

v-model on a custom component requires?

Question 6

Array prop default must be?

Question 7

v-bind="obj" on a component?

Question 8

Prop validator function returns?

Question 9

PropTypes in Vue are defined using?

Question 10

emit('change', value) in child is listened in parent with?

9. Interview Questions

  • Q: Why is direct prop mutation an anti-pattern in Vue?
  • Q: How do you implement v-model on a custom component in Vue 3?

10. Summary

Vue's component communication is one-directional and predictable. Props carry data DOWN from parent to child. defineEmits carries events UP from child to parent. Custom v-model combines both for a clean two-way binding API on form-like components. Always treat props as read-only.

11. Next Chapter Recommendation

In Chapter 8: Directives in Vue.js, we master all of Vue's built-in directives — v-bind, v-model, v-if, v-show, v-for — and create custom directives for DOM behavior.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·