🔥 (234) How NuxtErrorBoundary works, Nesting slots, Slot transitions, and Teleportation

​ ​

Read this on my blog

Hey everyone!

I've got a new article on how the NuxtErrorBoundary component works.

It's a deep dive into the source code, and I find it really interesting because it's doing a bunch of really neat Vue and Nuxt things all in a fairly small component.

Here's the article:

How NuxtErrorBoundary works

And as always, here are your tips!

— Michael

Nuxt Tips Collection

Master Nuxt without hours digging through docs. Learn what you need in just 5 minutes a day:

  • 117 practical tips to unlock hidden features of Nuxt
  • 14 chapters covering components, routing, SSR, testing and more
  • 3 daily tips for 3 months via email
  • 7 real-world code repos to learn from
  • Reviewed by Nuxt core team for accuracy
"Highly recommend Michael's Nuxt Tips Collection. He's one of the best Vue & Nuxt teachers I know." — Sébastien Chopin

Master Nuxt Today →

🔥 Nesting slots

As you start creating more abstractions with Vue, you may need to begin nesting your slots:

<!-- Parent.vue -->  <template>    <Child>      <!-- We take the content from the grand-parent slot            and render it inside the child's slot -->      <slot />    </Child>  </template>

This works similarly to how you would catch an error and then re-throw it using a try...catch block:

try {    // Catch the error    reallyRiskyOperation();  } (e) {    // Then re-throw as something else for the next    // layer to catch and handle    throw new ThisDidntWorkError('Heh, sorry');  }

Normally when using a slot we just render the content that's provided to us:

<template>    <div>      <!-- Nothing to see here, just a regular slot -->      <slot />    </div>  </template>

But if we don't want to render it in this component and instead pass it down again, we render the slot content inside of another slot:

<template>    <Child>      <!-- This is the same as the previous code example,           but instead of a `div` we render into a component. -->      <slot />    </Child>  </template>

🔥 Slot Transitions

It's possible to use transitions with slot content, but there's one key to making them work smoothly:

<!-- SlotWithTransition.vue -->  <template>    <!-- Creating the base slot with a transition -->    <transition name="fade" mode="out-in">      <slot></slot>    </transition>  </template>

Always make sure that content provided to the slot is keyed.

This helps Vue keep track of when to trigger the transition:

<template>    <SlotWithTransition>      <div v-if="isThisTrue" key="true">        This is true.      </div>      <div v-else key="false">        This is false.      </div>    </SlotWithTransition>  </template>

🔥 Teleportation

You can get an element to render anywhere in the DOM with the teleport component in Vue 3:

<template>    <div>      <div>        <div>          <teleport to="body">            <footer>              This is the very last element on the page            </footer>          </teleport>        </div>      </div>    </div>  </template>

This will render the footer at the very end of the document body:

<html>    <head><!-- ... --></head>    <body>      <div>        <div>          <div>            <!-- Footer element was moved from here... -->          </div>        </div>      </div>      <!-- ...and placed here -->      <footer>This is the very last element on the page</footer>    </body>  </html>

This is very useful when the logic and state are in one place, but they should be rendered in a different location.

One typical example is a notification (sometimes called a toast).

We want to be able to display notifications from wherever inside of our app. But the notifications should be placed at the end of the DOM so they can appear on top of the page:

<!-- DogList.vue -->  <template>    <div>      <DogCard        v-if="dogs.length > 0"        v-for="dog in dogs"        :key="dog.id"        v-bind="dog"      />      <teleport to="#toasts">        <!-- Show an error notification if we have an error -->        <Toast          v-if="error"          message="Ah shoot! We couldn't load all the doggos"        >      </teleport>    </div>  </template>

Which will render this to the DOM:

<html>    <head><!-- ... --></head>    <body>      <div id="#app">        <!-- Where our Vue app is normally mounted -->      </div>      <div id="toasts">        <!-- All the notifications are rendered here,             which makes positioning them much easier -->      </div>    </body>  </html>

Here's the complete documentation: https://vuejs.org/api/built-in-components.html#teleport

📜 Async and Sync: How useAsyncData Does It All

Have you ever noticed that useAsyncData can be used both sync and async?

What's up with that?

In this article, I explain how it works, and how you can use this pattern to your advantage.

Check it out here: Async and Sync: How useAsyncData Does It All

📜 Using OAuth in Nuxt

OAuth is tricky to understand, so I wanted to write something to help explain how it works.

Luckily, we can use auth without knowing OAuth, but it's still good to understand how it works.

This one is based on the Mastering Nuxt course I created with Vue School.

Check it out here: Using OAuth in Nuxt

💬 Humor

"Code is like humor. When you have to explain it, it's bad." — Cory House

🧠 Spaced-repetition: The limitations of props

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.

Props are helpful, but they have two glaring issues:

  • Impossible to pass markup*
  • Not that flexible

*not technically impossible, but not something you want to do.

The solution to these two problems is the same, but we'll get there in a second.

Many components you create are contentless components. They provide a container, and you have to supply the content. Think of a button, a menu, an accordion, or a card component:

<Card title="Shrimp Tempura">    <img src="picOfShrimp.jpg" />    <div>      <p>Here are some words about tempura.</p>      <p>How can you go wrong with fried food?</p>    </div>    <a href="www.michaelnthiessen.com/shrimp-tempura">      Read more about Shrimp Tempura    </a>  </Card>

You can often pass this content in as a regular String. But many times, you want to pass in a whole chunk of HTML, maybe even a component or two.

You can't do that with props.*

*again, yes, you could do this, but you'll definitely regret it.

Props also require that you plan for all future use cases of the component. If your Button component only allows two values for type, you can't just use a third without modifying the Button:

<!-- You just have to *believe* it will work -->  <Button type="AWESOME" />

The component doesn't allow for that...

<!-- Button.vue -->  <script setup>  defineProps<{    type: 'Primary' | 'Secondary' // AWESOME is not an option here  }>();  </script>

I'm not a psychic, and I'm guessing you aren't either.

The solution to these problems?

I think I gave it away with my card example above...

...slots!

Slots allow you to pass in whatever markup and components you want, and they also are relatively open-ended, giving you lots of flexibility. This is why in many cases, slots are simply better than props.

🔗 Want more Vue and Nuxt links?

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:

Unsubscribe

评论

此博客中的热门博文

丁薛祥在“77国集团和中国”气候变化领导人峰会上的致辞(全文)

The magic of scoped slots in Vue ✨ (3/4)