🔥 (#209) Shallow Refs, Calling Component Methods, and Scoped Slots

Hey all!

In less than a week Mastering Nuxt will be released!

This will be an "early access" at a good discount, with the rest of the lessons following in the weeks to come.

I'll also be doing a live workshop on Nuxt this Friday. You'll get to see how to do a few cool things with Nuxt, and I'll be showing off the demo app you'll build in Mastering Nuxt as well.

If you sign up now you can submit your questions! I'll be answering the most asked questions so make sure to get them in.

Sign up for this free workshop here: https://tally.so/r/w5MLQv?source=michaelthiessen

— Michael

🔥 Calling a Method from Outside of the Component

You can call a method from outside of a component by giving it a ref:

<!-- Parent.vue -->
<template>
<ChildComponent ref="child" />
</template>
<script setup>
const child = ref(null);
// Somewhere in the Parent component
child.value.method();
</script>

If you're using the Options API, your syntax is only slightly different:

<!-- Parent.vue -->
<template>
<ChildComponent ref="child" />
</template>
<script>
export default {
methods: {
myMethod() {
// This can be anywhere in the Parent component
this.$refs.child.method();
}
}
}
</script>

Let me explain this one a bit more.

Sometimes "best practices" don't work for what you're doing, and you need an escape hatch like this.

Typically, we communicate between components using props and events. Props are sent down into child components, and events are emitted back up to parent components.

<template>
<ChildComponent
:tell-me-what-to-do="someInstructions"
@something-happened="hereIWillHelpYouWithThat"
/>
</template>

Occasionally, you may need your parent to trigger a method in the child component. This is where only passing props down doesn't work as well.

You could pass a boolean down and have the child component watch it:

<!-- Parent.vue -->
<template>
<ChildComponent :trigger="shouldCallMethod" />
</template>
<script setup>
// Child.vue
import { watch } from 'vue';
const props = defineProps({
trigger: {
type: Boolean,
required: true
}
});
watch(props.trigger, (newVal) => {
if (newVal) {
// Call the method when the trigger is set to `true`
someMethodInChild();
}
});
</script>

This works fine, but only on the first call. If you needed to trigger this multiple times, you'd have to clean up and reset the state. The logic would then look like this:

  1. The Parent component passes true to trigger prop
  2. Watch is triggered, and the Child component calls the method
  3. The Child component emits an event to tell the Parent component that the method has been triggered successfully
  4. The Parent component resets trigger back to false, so we can do this all over again

Ugh.

Instead, if we set a ref on the child component we can call that method directly:

<!-- Parent.vue -->
<template>
<ChildComponent ref="child" />
</template>
<script setup>
const child = ref(null);
// Somewhere in the Parent component
child.value.method();
</script>

Yes, we're breaking the "props down, events up" rule and breaking encapsulation, but it's so much cleaner and easier to understand that it's worth it!

Sometimes the "best" solution ends up being the worst solution.

🔥 Understanding scoped slots

Here's the best way to think about scoped slots:

Scoped slots are like functions that are passed to a child component that returns HTML.

Once the template is compiled, they are functions that return HTML (technically vnodes) that the parent passes to the child.

Here's a simple list that uses a scoped slot to customize how we render each item:

<!-- Parent.vue -->
<template>
<ScopedSlotList :items="items">
<template v-slot="{ item }">
<!-- Make it bold, just for fun -->
<strong>{{ item }}</strong>
</template>
</ScopedSlotList>
</template>
<!-- ScopedSlotList.vue -->
<template>
<ul>
<li
v-for="item in items"
:key="item"
>
<slot :item="item" />
</li>
</ul>
</template>

We can rewrite this example to use a function instead of a scoped slot:

<!-- Parent.vue -->
<template>
<ScopedSlotList
:items="items"
:scoped-slot="(item) => `<strong>${item}</strong>`"
>
</template>
<!-- ScopedSlotList.vue -->
<template>
<ul>
<li
v-for="item in items"
:key="item"
v-html="scopedSlot(item)"
/>
</ul>
</template>

🔥 Shallow Refs

You can optimize the reactivity in your app by using shallowRef:

const user = shallowRef({
name: 'Michael',
friends: [
{
name: 'Travis',
friends: [
// ...
],
},
{
name: 'Matthew',
friends: [
// ...
],
},
]
});

Reactivity is only triggered when the value of the ref itself is changed:

// Triggers a reactive update
user.value = matthew;

But modifying any of the nested properties won’t trigger anything:

// Nothing happens
user.value.name = 'Martin';

Adding deep reactivity to a large object can cost you a lot of performance, so this can be useful for saving some CPU cycles.

You can also manually trigger a reactive update if it’s necessary:

// Log the user whenever it changes
watchEffect(() => console.log(user));
// Update nested state (no log happens)
user.value.name = 'Martin';
// Force a reactive update to trigger
triggerRef(user);
// [user object]

Here are the docs for shallowRef and triggerRef.

🎙️ #051 — Vite Inside Out (with Matias "Patak" Capeletto)

Vite is amazing, I think we can all agree on that statement. But what is Vite actually!? Why is it so great, faster than webpack and the "de-facto standard of the web" already?

Alex is joined by Vite Core Team member Matias Capeletto, better known as Patak, to talk about all these questions.

Learn about the future of Vite, how it uses two bundlers under the hood and why almost every framework adopted it. Enjoy the episode!

Watch on YouTube or listen on your favorite podcast platform.

Chapters:

In case you missed them:

📜 Data Fetching Basics in Nuxt

Nuxt offers a set of powerful built-in tools for handling data fetching.

It provides composable functions that make it easy to fetch data and automatically handle server-side rendering, client-side hydration, and error handling. This enables you to write clean and efficient code, ensuring an optimal user experience.

In this article we’ll examine the different methods Nuxt gives us for data fetching.

Check it out here: Data Fetching Basics in Nuxt

đź“… Upcoming Events

Here are some upcoming events you might be interested in. Let me know if I've missed any!

VueConf US 2025 — (May 13, 2025 to May 15, 2025)

Giving a talk here on component patterns! A great Vue conference, this year held in Tampa. Two days of conference talks, plus a day for workshops.

Check it out here

MadVue 2025 — (May 29, 2025)

It's time to get together in Madrid. Join for a full day of talks, activities, and networking with the Vue.js community and ecosystem.

Check it out here

đź’¬ Not Learning Javascript

"JavaScript is the only language that I’m aware of that people feel they don’t need to learn before they start using it." — Douglas Crockford

đź§  Spaced-repetition: Dynamic Returns

The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨‍🔬

Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.

A composable can either return a single value or an object of values. Typically, these values are refs.

But we can also dynamically switch between the two depending on what we want to use the composable for:

// Grab only the single value
const now = useNow()
// Get more granular access to the composable
const {
now,
pause,
resume
} = useNow({ controls: true })

This is great because we may only need a single value most of the time. So why complicate the interface for the main use case?

But by dynamically providing the full object of refs, we allow for more advanced use cases as well. Even if they are rarely used.

Here is how we might implement that:

export default useNow(opts) {
const {
controls = false,
} = opts;
// Do some things in your composable
if (controls) {
return { now, pause, resume };
} else {
return now;
}
}

Michael Hoffman curates a fantastic weekly newsletter with the best Vue and Nuxt links.

Sign up for it here.

p.s. I also have a bunch of products/courses: