Vue.js Certification

Bootcamp

Welcome back!

👋

Day 2

  • 7. Component Basics: Props
  • 8. Component Basics: Events
  • 9. Lifecycle hooks
  • 10. Transitions
  • 11. Slots
  • 12. Watchers
  • 13. Template refs
  • 14. Plugins

Section Topics

But first...

Any questions about the homework?

Day 2

Let's start learning the material for

🚦

Component Fundamentals: Props

7

Section

So far we've done everything at the page level

But websites are made up of distinct components

Header

Filters

Courses

Course Card

<TheHeader>
  <MainNav />
  <SiteSearch />
  <UserNav />
</TheHeader>

<CourseFilters/>

<TheCourses>
  <CourseCard title="The Vue.js 3 Masterclass"/>
  <CourseCard title="Nuxt.js 3 Fundamentals"/>
  <CourseCard title="TypeScript with Vue.js 3"/>
  <!-- ... -->
</TheCourses>

<TheFooter />

We can break site pieces up into separate files (SFCs) and assemble pages cleanly

<TheHeader>
  <MainNav />
  <SiteSearch />
  <UserNav />
</TheHeader>

<CourseFilters/>

<TheCourses>
  <CourseCard title="The Vue.js 3 Masterclass"/>
  <CourseCard title="Nuxt.js 3 Fundamentals"/>
  <CourseCard title="TypeScript with Vue.js 3"/>
  <!-- ... -->
</TheCourses>

<TheFooter />

We can pass components arguments  (ie. props) to make them flexible and reusable

<TheHeader>
  <MainNav />
  <SiteSearch />
  <UserNav />
</TheHeader>

<CourseFilters/>

<TheCourses>
  <CourseCard title="The Vue.js 3 Masterclass"/>
  <CourseCard title="Nuxt.js 3 Fundamentals"/>
  <CourseCard title="TypeScript with Vue.js 3"/>
  <!-- ... -->
</TheCourses>

<TheFooter />

We must also sometimes communicate between components

(this pseudo code doesn't show how, we'll learn more in a minute)

How do we create these components?

(Let's look at a simple example)

A HelloWorld Component

  • lives in the components directory by convention
  • consists of 3 sections (already discussed SFC structure)
  • this is a simple example. but can include complex markup, styles, or JS
// src/components/HelloWorld.vue
<script setup></script>

<template>
  <div>
	This is the component
  </div>
</template>

<style>
.course-card{/**/}
</style>
// App.vue, MyPage.vue, 
// or AnotherComponent.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>

<template>
  <HelloWorld />
  <!-- or -->
  <HelloWorld></HelloWorld>
</template>

Use by importing into the script setup of another component

Then use as often as needed

<!-- App.vue, MyPage.vue 
 or AnotherComponent.vue -->
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>

<template>
  <HelloWorld />
  <HelloWorld />
  <HelloWorld />
  <HelloWorld />
</template>

Then use as often as needed

Result

<!-- App.vue, MyPage.vue 
 or AnotherComponent.vue -->
<script setup>
import HelloWorld from './components/HelloWorld.vue';
</script>

<template>
  <HelloWorld />
  <HelloWorld />
  <HelloWorld />
  <HelloWorld />
</template>

Can make more flexible and re-usable with props

Let's look at a CourseCard component as an example

Let's look at a CourseCard component as an example

<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
  
console.log(props.title)
</script>

<template>
<div class="course-card">
  <img :src="img">
  <h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>

defineProps in script setup

Let's look at a CourseCard component as an example

<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
  
console.log(props.title)
</script>

<template>
<div class="course-card">
  <img :src="img">
  <h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>

capture return to use props in script section

Let's look at a CourseCard component as an example

<!-- /src/components/CourseCard.vue -->
<script setup>
const props = defineProps(['title', 'img']);
  
console.log(props.title)
</script>

<template>
<div class="course-card">
  <img :src="img">
  <h3>{{ title }}</h3>
</div>
</template>
<style>
.course-card{ /**/}
</style>

access props directly in the template

Using the CourseCard

// App.vue, MyPage.vue, 
// or AnotherComponent.vue
<script setup>
import CourseCard from './components/CourseCard.vue';
</script>

<template>
  <CourseCard title="The Vue.js 3 Masterclass" img="/mc.jpg"/>
  <CourseCard title="Nuxt.js 3 Fundamentals" img="/nuxt.jpg" />
  <CourseCard title="TypeScript with Vue.js 3" img="ts.jpg" />
</template>

Using the CourseCard

Result

<script setup>
import CourseCard from './components/CourseCard.vue';
import {ref} from "vue";
const courses = ref([ 
  { title: "The Vue.js 3 Masterclass", img :"/mc.jpg" },
  { title: "Nuxt.js 3 Fundamentals", img: "/nuxt.jpg" },
  { title: "TypeScript with Vue.js 3", img: "/ts.jpg" }
])

const test = ref("");
</script>
<template>
  <CourseCard 
    v-for="course in courses" 
    :key="course.title" 
    :title="course.title"
    :img="course.img"
  />
</template>

Pass Dynamic Values as Props

Bind Multiple Properties Using an Object

<script setup>
import CourseCard from './components/CourseCard.vue';
import {ref} from "vue";
const courses = ref([ 
  { title: "The Vue.js 3 Masterclass", img :"/mc.jpg" },
  { title: "Nuxt.js 3 Fundamentals", img: "/nuxt.jpg" },
  { title: "TypeScript with Vue.js 3", img: "/ts.jpg" }
])
</script>

<template>
  <CourseCard 
    v-for="course in courses" 
    :key="course.title" 
    v-bind="course"         
  />
</template>

What all is possible with

defineProps?

defineProps

<script setup>
// no import of `defineProps` necessary
// it's compile time only
defineProps(['title']);
</script>

Can specify as many as needed

defineProps

<script setup>
defineProps(
  ['title', 'inProgress', 'percentWatched']
);
</script>

defineProps

Can specify their types

(shows runtime warning in console if wrong type provided)

<script setup>
defineProps({
  title: String,
  inProgress: Boolean,
  percentWatched: String
  tags: Array
});
</script>

defineProps

<script setup>
defineProps({
  //...
  tags: [Array, String]
});
</script>

Support multiple types with an array

Provide other options like required and default

defineProps

<script setup>
defineProps({
  title: { type: String, required: true },
  // boolean default is false
  inProgress: { type: Boolean }, 
  percentWatched: { type: Number, default: 0 },
  // must define array and object defaults with a function
  tags: { type: [Array, String], default: (rawProps)=> [] }
});
</script>

Include custom validation

defineProps

defineProps({
  title: { 
    type: String, 
    required: true, 
    validator(value) {
      return value.length < 50;
    }, 
  },
  //...
});

Tips for working with props

Props bind data 1 way

const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'

Never mutate a prop

will log warning to console

const props = defineProps({
  foo: Object
})

props.foo.someProperty = 'bar'

Not even objects or arrays

No warning but still highly discouraged

(no warning because it is unreasonably expensive for Vue to prevent such mutations)

Boolean props shorthand

<CourseCard inProgress />
<!--same as-->
<CourseCard :inProgress="true" />

Including a boolean prop with no value will imply `true`.

Must bind to support non-strings

<!-- without binding it's a string-->
<CourseCard percentWatched="0.5" />
<!-- this properly provides a number -->
<CourseCard :percentWatched="0.5" />


<!-- same applies to arrays and other non-strings -->
<!-- don't do this -->
<CourseCard tags="['tag-1', 'tag-2']" />
<!-- do this -->
<CourseCard :tags="['tag-1', 'tag-2']" />

Fallthrough attributes and props are different

<!-- CustomInput.vue -->
<script setup>
defineProps({
  value: String
})

</script>

<template>
<input :value="value">	  
</template>
<!-- Parent.vue -->
<script setup>
import CustomInput from "@/components/CustomInput.vue";
</script>

<template>
  <CustomInput placeholder="Name" value="" />
</template>

value is a prop

placeholder is a fallthrough attribute

Questions?

🙋🏾‍♀️

Exercise 7

👩‍💻👨🏽‍💻

Coffee Break

☕️

Component Fundamentals: Events

8

Section

Props are for passing data down to children components

Events are for passing data up to parent components

Imagine the User Card is Editable

(from the last challenge)

<script setup> 
const props = defineProps({
  username: {
    type: String,
    required: true,
  },
});
  
</script>

<template>
  <input v-model="username" > 	
  <!-- ... -->
</template>

We can not directly mutate the prop

So how do we change the user data in the parent from within the child?

We don't!
The parent is responsible for it's own data.

BUT we can emit an event so the parent can can choose how to respond

initial value is the username prop

emit an event on the input event

<script setup> 
const props = defineProps({
  username: {
    type: String,
    required: true,
  },
});
  
</script>

<template>
  <input 
     :value="username" 
     @input="$emit('update:username', $event.target.value)" 
  > 	
  <!-- ... -->
</template>

How to Emit Anatomy

call $emit() function

pass a payload

name the event

 @input="$emit('update:username', $event.target.value)" 

Listen for the event in the parent

 <UserProfileCard @update:username="user.username = $event" />

get the payload

just like native event listeners

Declaring Emitted Events

<!-- UserProfileCard -->
<script setup>
defineEmits(["update:username"]);
//...
</script>

<template>
<input
  type="text"
  :value="username"
  @input="$emit('update:username', $event.target.value)"
/>
</template>

Declare all events in script section

Why declare emits?

Why declare emits?

  • documents all the components events in 1 place
  • let's you emit an event from within script setup section
  • can provide payload validation
  • If using TS, it brings error detection and autocompletion to events
  • allows Vue to exclude known listeners from fallthrough attributes

Text

<!-- UserProfileCard -->
<script setup>
const emit = defineEmits(["update:username"]);
  
const updateUsername = (value)=> emit('update:username', value);
//...
</script>

<template>
<input
  type="text"
  :value="username"
  @input="updateUsername($event.target.value)"
/>
</template>

emit an event from within script setup section

Text

<!-- UserProfileCard -->
<script setup>
const emit = defineEmits({
  'update:username': (payload)=>{
    return payload !== ''
  }
})
 
//...
</script>

Payload validation

method with the name of the event

pass object instead of array

Text

<!-- UserProfileCard -->
<script setup>
const emit = defineEmits({
  'update:username': (payload)=>{
    return payload !== ''
  }
})
 
//...
</script>

Payload validation

method takes the event payload

Return false for invalid or true for valid

Events Tips

Events Tips

  • component events don't bubble (they're only accessible in the parent)
  • payload isn't required

v-model on components

<!--MyComponent.vue-->
<script setup> 
defineProps({
  modelValue: String
});

defineEmits(['update:modelValue'])  
</script>
<!--Parent.vue-->
<MyComponent modelValue="someData" @update:modelValue="someData = $event" />

v-model on components

<!--MyComponent.vue-->
<script setup> 
defineProps({
  modelValue: String
});

defineEmits(['update:modelValue'])  
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>

Shorthand when Used

v-model on components

<!--MyComponent.vue-->
<script setup> 
const modelValue = defineModel()
// modelValue.value = "newValue" // emits update:modelValue
</script>
<!--Parent.vue-->
<MyComponent v-model="someData"/>

Shorthand when Defined

v-model on components

<!--MyComponent.vue-->
<script setup> 
defineProps({
  title: String
});

defineEmits(['update:title'])  
</script>
<!--Parent.vue-->
<MyComponent title="someData" @update:title="someData = $event" />

v-model on components

<!--MyComponent.vue-->
<script setup> 
defineProps({
  title: String
});

defineEmits(['update:title'])  
</script>
<!--Parent.vue-->
<MyComponent v-model:title="someData"/>

Shorthand when Used

v-model on components

<!--MyComponent.vue-->
<script setup> 
const title = defineModel("title")
// title.value = "newValue" // emits update:title
</script>
<!--Parent.vue-->
<MyComponent v-model:title="someData"/>

Shorthand when defined

Questions?

🙋🏾‍♀️

Exercise 8

👩‍💻👨🏽‍💻

Lifecycle Hooks

All components go through various steps throughout their lifespan

We can run custom code at different points of that lifespan with lifecycle hooks

This is what the lifecycle looks like

Let's zoom into the different hooks

Let's breakdown each step

Why?

Just do it at the root level of script setup

When using script setup we don't really use:

  • beforeCreate
  • created
  • beforeMount
  • elements exist in DOM
  • only runs in browser (no SSR)

Mounted Practical Use Cases

  • add manual event listeners to the DOM (sometimes can't use an event because the element to listen to is outside of the component (like the window)
  • fetch data only on the client (in SSR applications)
  • Check the width of a rendered DOM element to use for some calculation
  • access the DOM state before Vue updates the DOM
  • safe to modify component state
  • I rarely use
  • called after any DOM update of the component
  • ⚠️ Do not mutate component state in the updated hook
  • Useful for direct DOM access
  • called right before a component is unmounted
  • the component instance is still fully functional
  • I rarely use
  • after the component and it's children are removed from DOM
  • all reactive effects are stopped
  • useful for cleaning up side effects

Unmounted Use Cases

  • cleaning up any manual event listeners
  • cleaning up any intervals
  • cleaning up any subscriptions to server events

Usage with script setup

import { onMounted, onUnmounted, onUpdated } from "vue";

onMounted(()=>{
  // do whatever
})

first import from vue

Then call the function with a callback

Call Syncronously

import { onMounted, onUnmounted, onUpdated } from "vue";

onMounted(()=>{
  // do whatever
})

⚠️ NOTE: you should call the lifecycle hooks syncronously inside of setup

setTimeout(() => {
  onMounted(() => {
    // this won't work.
  })
}, 100)

This would not work

Call Syncronously

Call Syncronously

  • Doesn't mean you can't use lifecycle hooks in a composable
  • Just make sure it's called synchronously in the composable and the composable is called synchronously in the component

A Practical lifecycle hooks example

Questions?

🙋🏾‍♀️

Exercise 9

👩‍💻👨🏽‍💻

Transitions

Section

10

Vue has a built-in <Transition> component for handling smooth transitions between various states

Use it to transition a single element entering or leaving the page

Also works with v-show

Or transitioning between multiple elements as long as only 1 is visble at a time

Transition can be different styles based on CSS

  • First examples faded in and out
  • The last example, elements moved up and faded out

🤔 How does that work?

🤔 How does that work?

These styles power the fade transition

Vue strategically adds these classes to the element

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

🤔 How does that work?

Added before the element is inserted, removed one frame after the element is inserted.

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

🤔 How does that work?

Applied during the entire entering phase.

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

This class can be used to define the duration, delay and easing curve for the entering transition

🤔 How does that work?

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

Ending state for enter. Added one frame after the element is inserted

Not needed in this example because opacity 1 is the elements default

🤔 How does that work?

.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

The same thing happens on leave

So what would fading in and moving down from the top look like?

So what would fade in down look like?

.v-enter-active,
.v-leave-active {
  transition: all 0.5s;
}

.v-enter-from,
.v-leave-to {
  transform: translateY(-50px);
  opacity: 0;
}

So what would fade in down look like?

.v-enter-active,
.v-leave-active {
  transition: all 0.5s;
}

.v-enter-from,
.v-leave-to {
  transform: translateY(-50px);
  opacity: 0;
}

A slight tweak can give a different effect

.v-enter-active,
.v-leave-active {
  transition: all 0.5s;
}
.v-enter-from{
  transform: translateY(-50px);
  opacity: 0;
}
.v-leave-to {
  transform: translateY(50px);
  opacity: 0;
}

The <Transition> component has lots more to offer

  • Named transitions
  • JavaScript Hooks
  • Transitions on appear
  • Transition modes
  • but we've covered enough to get you through the exam

1 More Component That Helps with Transitions

<TransitionGroup>
<TransitionGroup>

animates the insertion, removal, and order change of elements or components that are rendered in a list.

Example

Questions?

🙋🏾‍♀️

Exercise 10

👩‍💻👨🏽‍💻

Slots

Section

11

Props are great for making flexible components

Sometimes they fall a little short though

Button Example

<MyButton label="Click Me" />

Imagine we have a button that starts off simple

Button Example

But sometimes we'd like to include an icon

<MyButton 
  label="Click Me" 
  icon="mdi:home"        
/>

Button Example

<MyButton 
  label="Click Me" 
  icon="mdi:home"  
  :boldFirstWord="true"        
/>

And for this one, we'd like to bold the first word

We're doing a lot to customize the HTML that goes inside the button

That's what slots are good for!

<MyButton>
  <Icon icon="mdi:home"></Icon>
  <span><strong>Click</strong> Me</span>
</MyButton>

Let the parent customize what goes inside based on the use case

How do we write the component to handle this sytnax?

<!--MyButton.vue -->
<template>
  <button class="btn">
    <slot></slot>
  </button>
</template>

provide a slot outlet that indicates where the parent-provided slot content should be rendered

<!--MyButton.vue -->
<template>
  <button class="btn">
    <slot></slot>
  </button>
</template>

so with this definition

and this usage

<MyButton>
  <Icon icon="mdi:home"></Icon>
  <span><strong>Click</strong> Me</span>
</MyButton>
<button class="btn">
   <svg> 
     <!-- the rendered Icon component here, 
     internals not important -->
  </svg>
  <span><strong>Click</strong> Me</span>
</button>

This is rendered

<!--MyButton.vue -->
<template>
  <button class="btn">
    <slot>Default Label</slot>
  </button>
</template>

provide default slot content

with this definition

and this usage

<MyButton/>
<button class="btn">Default Label</button>

This is rendered

<!--MyButton.vue -->
<template>
  <button class="btn">
    <slot>Default Label</slot>
  </button>
</template>

Name slots to provide multiple outlets

<!-- SiteLayout.vue-->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Pass content to the outlets in the parent with template and v-slot

<template>
  <!-- Parent.vue-->
  <SiteLayout>
    <template v-slot:header> Hello Header </template>

    Hello default

    <template #footer> Hello Footer </template>
  </SiteLayout>
</template>

# shorthand

default doesn't need template tags

<template>
  <!-- Parent.vue-->
  <SiteLayout>
    <template v-slot:header> Hello Header </template>

    <template #default>Hello default</template>

    <template #footer> Hello Footer </template>
  </SiteLayout>
</template>

Can optionally use template for default slot

The rendered output

<div class="container">
  <header>
    Hello header
  </header>
  <main>
 	Hello default
  </main>
  <footer>
    Hello Footer
  </footer>
</div>
<!-- Parent.vue-->
<script setup>
import {ref} from "vue";
const headerSlotName = ref("header");
const footerSlotName = ref("foooter");
</script>
<template>
  <SiteLayout>
    <template v-slot:[headerSlotName]> Hello Header </template>

    <template #default>Hello default</template>

    <template #[footerSlotName]> Hello Footer </template>
  </SiteLayout>
</template>

Can set slots in parent dynamically with []

<!-- SiteLayout.vue-->
<script setup>
import {ref} from "vue"
const dynamicHeaderSlotName= ref("header")
</script>
<div class="container">
  <header>
    <slot :name="dynamicHeaderSlotName"></slot>
  </header>
 <!-- .... -->
</div>

Can even make slot names dynamic for the defintion

Scoped Slots

Let's look at

Slot content has access to the data scope of the parent component

<!-- MyParent.vue -->
<script setup>  
const label = ref("Click Me")
</script>
<MyButton>
  <Icon icon="mdi:home"></Icon>
  <span>{{ label }}</span>
</MyButton>

Slot content does not have access to the child component's data.

<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>

<template>
<button class="btn" >
  <slot></slot>
</button>
</template>
<!-- MyParent.vue -->
<MyButton>
  <Icon icon="mdi:home"></Icon>
  <span>{{buttonData}}</span>
</MyButton>

<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>

<template>
<button class="btn" >
  <slot></slot>
</button>
</template>

Can we expose buttonData to the parent's slot content?

🤔

<!-- MyButton.vue-->
<script setup>
import {ref} from "vue";
const buttonData = ref("some random data")
</script>

<template>
<button class="btn" >
  <slot :data="buttonData"></slot>
</button>
</template>

Yes! Just pass it like a prop

<!-- MyParent.vue-->
<template>
  <ButtonWithData>
    <template #default="{ data }">
      <span>
        {{ data }} <!-- some random data -->
      </span>
    </template>
  </ButtonWithData>
</template>

In the parent all data given as the value to the slot name

<!-- MyButton.vue-->
 <slot :data="buttonData" otherData="something else"></slot>
<!-- MyParent.vue-->
<template>
  <ButtonWithData>
    <template #default="{ data, otherData }">
      <span>
        {{ data }} <!-- some random data -->
        {{ otherData }} <!-- something else -->
      </span>
    </template>
  </ButtonWithData>
</template>

Can pass multiple pieces of data

using template works with default and named slots

<!-- MyParent.vue-->
<template>
  <ButtonWithData>
    <template #default="{ data, otherData }">
      <span>
        {{ data }} <!-- some random data -->
        {{ otherData }} <!-- something else -->
      </span>
    </template>
  </ButtonWithData>
</template>
<!-- MyParent.vue-->
<template>
  <ButtonWithData v-slot="{ data, otherData }">
      <span>
        {{ data }} <!-- some random data -->
        {{ otherData }} <!-- something else -->
      </span>
  </ButtonWithData>
</template>

can shorten for default slots

<!-- MyParent.vue-->
<template>
  <ButtonWithData #="{ data, otherData }">
      <span>
        {{ data }} <!-- some random data -->
        {{ otherData }} <!-- something else -->
      </span>
  </ButtonWithData>
</template>

or even just the #

<!-- MyParent.vue-->
<template>
  <ButtonWithData #="{ data, otherData }">
      <span>
        {{ data }} <!-- some random data -->
        {{ otherData }} <!-- something else -->
      </span>
  </ButtonWithData>
</template>

⚠️ WARNING: this only works when NO named slots

Scoped Slots Practical Example

Questions?

🙋🏾‍♀️

Exercise 11

👩‍💻👨🏽‍💻

Coffee Break

☕️

Watchers

12

Section

Vue provides a couple different functions for watching reactive data and then performing side effects when that data changes

Some practical use cases:

  1. Showing a notification when some data is freshly updated
const myDataFromTheBackend = ref({...})
watch(myDataFromTheBackend, {
  alert("Hello, your friend just updated some data")
}, { deep: true })

Some practical use cases:

2. Emitting an event when local data changes

const props = defineProps({
  value: String
})
const emit = defineEmits(['update:value'])
const localValue = ref(props.value)

watch(localValue, ()=> emit('update:value', localValue))

Some practical use cases:

3. Making a fetch request when data changes

const posts = ref([...])
const sortOrder = ref("desc");

watch(sortOrder, fetchPosts)

function fetchPosts(){
  // call to api for posts and set data
  posts.value = postsFromServer
}

Some practical use cases:

4. Using a Browser API

const cookieAccepted = ref(localStorage.getItem() || false);

watch(cookieAccepted, (accepted)=>{
  localStorage.setItem("cookieAccepted", accepted)
})
  1. watch()
  2. watchEffect()

The 2 watcher functions

watch()

watchEffect()

  • Used to perform side effects
  • manually register reactive dependencies
  • lazy by default
  • auto cleaned up on component unmount
  • Used to perform side effects
  • reactive dependencies in callback automatically registered
  • eager by default
  • auto cleaned up on component unmount

watch() example

const myTeamsScore = ref(7);

watch(
  myTeamsScore, 
  (myScore) => alert("My Score has changed")
);

data to watch

(can be a reactive ref)

watch() example

const scores = reactive({ myTeam: 7 });

watch(
  scores, 
  (scores) => alert("My Score has changed")
);

(or can be reactive)

const myTeamsScore = ref(7);
const scoreDoubled = computed(()=> myTeamsScore.value * 2)
watch(
  scoreDoubled, 
  (doubled) => alert("My Score has changed")
);

watch() example

(or a computed prop)

watch() example

const myTeamsScore = ref(7);

watch(
  myTeamsScore, 
  (myScore) => alert("My Score has changed")
);

Callback function

data to watch

watch() example

const myTeamsScore = ref(7);

watch(
  myTeamsScore, 
  (myScore) => alert("My Score has changed")
);

updated value is passed (not a ref)

watch() example

const myTeamsScore = ref(7);
const otherTeamsScore = ref(0);

watch([myTeamsScore, otherTeamsScore], 
  ([myScore, otherScore]) => {
  	console.log(myScore, otherScore); // 7, 1
});

watch multiple values by passing an array of refs

watch() example

Then values are passed as an array to the callback in the same order

(can de-structure)

const myTeamsScore = ref(7);
const otherTeamsScore = ref(0);

watch([myTeamsScore, otherTeamsScore], 
  ([myScore, otherScore]) => {
  	console.log(myScore, otherScore); // 7, 1
});

watch() example

const scores = ref([7, 0]);
watch(
  scores,
  (scores) => {
    console.log(scores); // [7, 1]
  },
);

Can also watch a ref that is an array

watch() example

But you must provide the deep option

const scores = ref([7, 0]);
watch(
  scores,
  (scores) => {
    console.log(scores); // [7, 1]
  },
  {
    deep: true
  }
);

watch() example

const scores = ref({
  myTeamsScore: 7,
  otherTeamsScore: 3
});

watch(
  scores,
  (scores) => {
    console.log(scores);
  },
  {
    deep: true
  }
);

Same applies to an object

watch() example

const scores = ref({
  myTeamsScore: 7,
  otherTeamsScore: 3
});

watch(
  ()=> scores.value.myTeamsScore,
  (myTeamsScore) => {
    console.log(myTeamsScore);
  }
);

Watch only what's necessary in an object using a getter
(without the deep option)

const myTeamsScore = ref(7);

watch(
  myTeamsScore, 
  (myScore) => alert("My Score has changed")
);

watch() example

Watch is lazy so the callback will only run after myTeamsScore changes

const myTeamsScore = ref(7);

watch(
  myTeamsScore, 
  (myScore) => alert("My Score has changed"),
  {
    immediate: true
  }
);

watch() example

Use immediate option to run callback as soon as watcher is defined 

watchEffect()

const myTeamsScore = ref(7);
watchEffect(() => {
  console.log(myTeamsScore.value);
});

watchEffect() example

Only provide a callback as dependencies are automatically registered

const myTeamsScore = ref(7);
watchEffect(() => {
  // logs 7 immediately
  console.log(myTeamsScore.value); 
});

watchEffect() example

is eager, so callback runs immediately

const myTeamsScore = ref(7);
watchEffect(() => {
  // logs 7 immediately
  console.log(myTeamsScore.value); 
}, {
  immediate: false
});

watchEffect() example

cannot make lazy!

const footballScore = ref({
  myTeam: 7,
  otherTeam: 0,
});

watchEffect(
  () => console.log(footballScore.value);
);

watchEffect() example

always watches deeply

const footballScore = ref({
  myTeam: 7,
  otherTeam: 0,
});

watchEffect(
  () => console.log(footballScore.value),
  {
    deep: false
  }
);

watchEffect() example

CANNOT turn off deep

Both can be manually stopped

// will onlyl run once
// since it unwatches the first time
const unwatch = watchEffect(() =>{
  console.log(myTeamsScore.value)
  unwatch()
});
//same here
const unwatch = watch(myTeamScore, () =>{
  console.log(myTeamsScore.value)
  unwatch()
});

Could also unwatch on some user event

const unwatch = watchEffect(() =>{
  console.log(myTeamsScore.value)
});

function onClick(){
  unwatch()
}
import { watchEffect } from 'vue'

// this one will be automatically stopped
watchEffect(() => {})

// ...this one will not!
let unwatch;
setTimeout(() => {
  unwatch = watchEffect(() => {})
}, 100)

// manual cleanup here
onUnmounted(unwatch)

Can also manually clean up asynchronously defined watchers

import { watchEffect } from 'vue'

// this one will be automatically stopped
watchEffect(() => {})

// ...this one will not!
let unwatch;
setTimeout(() => {
  unwatch = watchEffect(() => {})
}, 100)

// manual cleanup here
onUnmounted(unwatch)

Can also manually clean up asynchronously defined watchers

This is very rare

Questions?

🙋🏾‍♀️

Exercise 12

👩‍💻👨🏽‍💻

Template Refs

13

Section

Template refs give us direct access to DOM elements

<script setup>
import { onMounted, ref } from "vue";
const el = ref();

onMounted(() => {
  console.log(el.value.innerText);
});
</script>
<template>
  <div>
    <p ref="el">Hello World</p>
  </div>
</template>

This is the template ref

It refers to this DOM element

Once the component mounts we can use the DOM element just like we would with vanilla JS

Can also get an array of template refs with v-for

<script setup>
import { ref, onMounted } from "vue";
const elements = ref([]);

onMounted(() => {
  elements.value.forEach((el) => {
    console.log(el);
  });
});
</script>

<template>
  <div v-for="n in 10" :key="n" ref="elements">
    {{ n }}
  </div>
</template>

Practical Use Cases

  • reading DOM element properties like their clientHeight or clientWidth
  • calling DOM element methods like blur and focus

Template Refs can also directly access child components

<!--ModalOverlay.vue-->
<script setup>
import { defineExpose, ref } from "vue";
const active = ref(false);

function toggle() {
  active.value = !active.value;
}
defineExpose({
  toggle,
});
</script>
<template>
  <div v-if="active">...</div>
</template>

Let's say we have a modal overlay component that exposes a toggle function

In the parent we can use a template ref to access the ModalOverlay and call it's exposed function

<script setup lang="ts">
import { ref } from "vue";
import ModalOverlay from "@/components/ModalOverlay.vue";
const modal = ref();
</script>
<template>
  <ModalOverlay ref="modal" />
  <button @click="modal.toggle()">Toggle Modal</button>
</template>

Questions?

🙋🏾‍♀️

Exercise 13

👩‍💻👨🏽‍💻

🎉

That's all for day 2

Homework

Continue practicing the concepts we learned today if you choose

Official Training Module

Resources for Helping to Complete Homework

🙋🏾‍♀️

Any final questions?

👋

See you next week!

Bootcamp Level 1 Day 2

By Daniel Kelly

Bootcamp Level 1 Day 2

  • 236