Props and Events

By Syncfusion®, Inc. · Last updated

defineProps() with runtime and type-based declarations, prop types, required props, defaults and validators, reactive props destructure, one-way data flow, deriving local values, $emit and defineEmits(), typed events, component v-model with defineModel(), many models and modifiers, and how components talk.

Talking Between Components

  • A parent often needs to give data to a child.
  • A child often needs to tell the parent something happened.
  • Props = data passed down from parent to child.
  • Events = messages sent up from child to parent.

Animation: the parent App sends props name and price down to DishCard. DishCard sends an add event back up to App

App (parent)

DishCard (child)

props ↓ name, price events ↑ add

Props go down. Events go up.

Simple lesson: props down, events up.

A Real-World Example

  • Meera's menu uses one DishCard for every dish.
  • The parent passes each card a name and a price.
  • When someone taps Add, the card tells the parent, and the parent updates the cart.
  • What happened: each card got its own name and price.
  • Add sent an add event with the dish name. The parent pushed it into the cart.
  • Switch the top bar to JavaScript to see the same component without types.

What You Will Learn

  1. How to declare props with defineProps().
  2. Two ways to declare them: runtime options and TypeScript types.
  3. Required props, defaults and validators.
  4. Why a child must not change its props.
  5. How to send events with $emit and defineEmits().
  6. How to send data with an event.
  7. How to make your own component work with v-model.
  8. How to use many v-models and custom modifiers.
  9. Which way to pass data in each situation.

defineProps()

  • defineProps() lists the props a component accepts.
  • It is a compiler macro: you do not import it.
  • It returns an object with the prop values. In the template, use the names directly.
  • Pass a fixed text: name="Idli". Pass JavaScript (numbers, variables): :price="50".
  • Name props in camelCase in the script (dishName). In templates both :dishName and :dish-name work.
  • A Boolean prop written with no value is true: <DishCard featured />. Left out, it is false.

Runtime Declaration

  • Describe each prop with an object: type, required, default and validator.
  • This works in TypeScript and JavaScript, and Vue checks the values while the app runs.
  • A default for an object or array must be a function that returns it.

A failed check, such as a missing required prop or a validator that returns false, gives a Vue warning in the Console. It does not stop the app. In production builds, these checks are skipped.

Type-Based Declaration (TypeScript)

  • In TypeScript, give the props as a type: defineProps<{ name: string; price?: number }>().
  • ? makes a prop optional.
  • Since Vue 3.5, you can destructure props and give defaults in one line. They stay reactive.
  • Older code uses withDefaults(defineProps<...>(), { price: 0 }) for the same job.
  • The JavaScript flavour shows the runtime version of the same component.

Simple lesson: list props with defineProps(). Use a type in TypeScript, or runtime options anywhere.

One-Way Data Flow

  • Props flow down only. When the parent's value changes, the child gets the new value.
  • A child must never change a prop. Props are read-only.
  • If a child changes a prop, Vue warns, and the parent may overwrite it later.
  • Need to change it? Copy it into a local ref, or work out a new value with computed.
  • A copy (ref(props.price)) takes the starting value only. If the parent changes the prop later, the copy does not follow. Use computed when it must follow.
  • What happened: "+1 (local)" changed the child's own copy. That is fine.
  • "Change the prop" was blocked, and Vue wrote a warning in the Console.

Simple lesson: never change a prop. Copy it, or derive from it.

A local copy made with ref(props.start) does not follow later changes from the parent. Use computed when the child must always match the prop.

Events

  • A child sends an event with $emit('name', data) in the template.
  • In the script, declare events with defineEmits(). It returns an emit function.
  • The parent listens with @name="handler", like a normal DOM event.
  • Extra arguments to emit reach the handler. In an inline handler they are $event.
  • What happened: each star button emitted rate with two values.
  • The parent's onRate(stars, dish) received both.
  • Clear used $emit straight from the template.

In TypeScript, defineEmits<{ rate: [stars: number] }>() types each event's data. Calling emit('rate', 'five') is then marked as an error in your editor. Runtime validation is also possible with an object of functions.

Simple lesson: declare events with defineEmits(). Send them with emit(). Listen with @.

v-model on Components

  • v-model on a component = a prop down + an update event up.
  • Since Vue 3.4, defineModel() creates both for you.
  • It returns a ref. Change it in the child, and the parent's ref changes too.
  • v-model:name gives a model a name, so one component can have many.

Custom Modifiers

  • A parent can add its own modifier: v-model.capitalize="dish".
  • The child reads it from the second value of defineModel().
  • A set function changes the value before it reaches the parent.

Simple lesson: defineModel() gives a component v-model support in one line.

Which Way to Pass Data?

NeedUseLesson
Parent gives data to a childPropsthis lesson
Child tells the parent something happenedEventsthis lesson
Two-way value between parent and childv-model with defineModel()this lesson
Many levels down, skipping the middleprovide / injectSlots, Attributes and Provide/Inject
Data shared by far-apart componentsA Pinia storeState Management with Pinia

Simple lesson: props down, events up, v-model for both, and other tools for long distances.

TL;DR

  • Props = data passed down. Declared with defineProps().
  • Runtime props = { type, required, default, validator }. Works in JS and TS.
  • Type-based props = defineProps<{ name: string }>(). TypeScript only.
  • Props destructure = const { price = 0 } = defineProps(...) stays reactive (Vue 3.5).
  • One-way data flow = a child never changes its props.
  • Events = emit('name', data) up. @name in the parent.
  • defineEmits() = declares events. Types each event's data in TS.
  • defineModel() = a prop + update event as one ref, for v-model.
  • v-model:name = many models on one component.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Changing a prop in the childProps are like local variablesCopy into a ref, or emit an eventA warning, and the parent overwrites it
price="50" for a numberVue guesses the typeWrite :price="50"The child gets the text "50"
Object default without a functiondefault: [] is finedefault: () => []All instances share one array
Forgetting defineEmitsemit exists by itselfconst emit = defineEmits([...])emit is not defined
Listening for an event the child never sendsAny @name worksMatch a name from defineEmitsThe handler never runs
Type-based props in a .js fileTypes work everywhereUse runtime options in JavaScriptA syntax error in JavaScript
Local ref for v-model in the childThe parent sees the local refUse defineModel()The parent's value never changes

Check Yourself

Exercises

What to Remember

  • Props go down, events go up.
  • Declare props with runtime options, or with a type in TypeScript.
  • Never change a prop. Copy it, derive from it, or emit an event.
  • defineEmits() declares events. emit('name', data) sends them.
  • defineModel() makes a component work with v-model.

Next: Slots, Attributes and Provide/Inject