🔥 (218) useTemplateRef article, Hybrid API, and Forcing Updates

​ ​

Read this on my blog

Hey there!

New article for you this week!

I went deep into the useTemplateRef composable, and how it compares to the old way of accessing DOM elements:

How to access DOM elements with useTemplateRef

And remember, the 35% sale for Clean Components Toolkit ends tomorrow!

Get Clean Components Toolkit now

We also just released a bunch of lessons for Mastering Nuxt, finishing up the chapter on data fetching:

  • Simplify Data Fetching with useFetch
  • Manage Data Reactively with useState and useFetch
  • When to use $fetch, useFetch, and useAsyncData
  • Add AI-Generated Chat Titles with a Typewriter Animation
  • Complete the Data Fetching for your Nuxt Chat App

The course now has 70+ lessons! The next chapters will focus on advanced topics, diving deeper into Nuxt, and improving the features we've already built.

And of course, I've got some tips for you to enjoy!

— Michael

🔥 Async Without Await

Using async logic with the composition API can be tricky at times.

We need to put things in the correct order, or the await keyword will mess things up with our reactivity.

But with the Async Without Await pattern, we don't need to worry about all of this:

const title = ref('Basic Title');  // We can place this async function wherever we want  const { state } = useAsyncState(fetchData());  const betterTitle = computed(() => `${title.value}!`);

Here's how this works:

  • We hook up all of our refs synchronously
  • Updates happen asynchronously in the background
  • Because of reactivity, everything "just works"

Here's a basic sketch of what the useAsyncState composable from VueUse is doing to implement this:

export default useAsyncState(promise) {    // 1. Create state ref synchronously    const state = ref(null);      const execute = async () => {      // 3. Reactivity will update this when it resolves      state.value = await promise;    }      // 2. Execute promise asynchronously in the background    execute();    return state;  }

🔥 Hybrid API: Composition API + Options API

You don't have to decide between Options API and Composition API, you can use both:

export default {    setup() {      const darkMode = ref(false);      return { darkMode }    },    methods: {      saveDarkMode() {        localStorage.setItem('dark-mode', this.darkMode);      },    }  };

We can also update values from the Options API:

export default {    setup() {      const darkMode = ref(false);      return { darkMode }    },    methods: {      changeTheme(val) {        this.darkMode = val;      }    }  };

Although you can access Composition API from the Options API, it's a one-way street. The Composition API cannot access anything defined through the Options API:

export default {    setup() {      const darkMode = ref(false);        // We can't access the method      this.changeTheme(true);        return { darkMode }    },    methods: {      changeTheme(val) {        this.darkMode = val;      }    }

This can be useful for incremental adoption of the Composition API, because it lets you use reusable composables in your Options API.

But mixing two styles of writing components is likely to cause more headaches than it solves, so please think twice before doing this!

🔥 Forcing a Component to Update

What do you do if a component isn't updating the way it should?

Likely, this is caused by a misunderstanding and misuse of the reactivity system.

But let's look at a quick solution using forceUpdate:

import { getCurrentInstance } from 'vue';    const methodThatForcesUpdate = () => {    // ...    const instance = getCurrentInstance();    instance.proxy.forceUpdate();    // ...  };

Using the Options API instead:

export default {    methods: {      methodThatForcesUpdate() {        // ...        this.$forceUpdate();  // Notice we have to use a $ here        // ...      }    }  }

Now, here comes the sledgehammer if the previous approach doesn't work.

I do not recommend using this approach. However, sometimes you just need to get your code to work so you can ship and move on.

But please, if you do this, keep in mind this is almost always the wrong way, and you're adding tech debt in to your project.

We can update a componentKey in order to force Vue to destroy and re-render a component:

<template>    <MyComponent :key="componentKey" />  </template>    <script setup>  import { ref } from 'vue';  const componentKey = ref(0);    const forceRerender = () => {    componentKey.value += 1;  };  </script>

The process is similar with the Options API:

export default {    data() {      return {        componentKey: 0,      };    },    methods: {      forceRerender() {        this.componentKey += 1;      }    }  }

You can find a deeper explanation here: https://michaelnthiessen.com/force-re-render/

🎙️ #057 — Motion for Vue (with Matt Perry)

In this episode of DejaVue, Michael and Alex chat with Matt Perry, the creator of Motion (formerly Framer Motion), about animations in web development.

Matt talks about his journey from designing gaming magazines as a kid to becoming a full-time developer, sharing his experiences with ActionScript, jQuery, and how Motion was born. They discuss how simple and powerful Motion's API is, its ability to work with different frameworks, and how it stacks up against other animation libraries like GSAP and AnimeJS.

But the episode also covers a lot of open source aspects, such as the challenges of keeping Motion going! 

Matt explains how Motion+ funds the development of Motion with one-time payments and time-gated (then publicly available) content, to support the project.

Watch on YouTube or listen on your favorite podcast platform.

Chapters:

In case you missed them:

📜 Prisma with Nuxt: Setting up Prisma with Supabase (1 of 5)

Trying to manage database schemas alongside your Nuxt app types can be a challenge.

But with Prisma, most of these problems go away.

It handles all of the boilerplate and coordination, so you just write one single schema that's used in your database and in your TypeScript app.

This is the first article in a series dedicated to showing you how to use Prisma in your Nuxt app.

Check it out here: Prisma with Nuxt: Setting up Prisma with Supabase (1 of 5)

📜 Adding Drag and Drop (video)

In this video from LearnVue, we see how easy it can be to add a powerful drag and drop system in to your app.

Check it out here: Adding Drag and Drop (video)

📅 Upcoming Events

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

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

Frontend Nation 2025 — (June 3, 2025 to June 5, 2025)

The epic online gathering for frontend developers! I'll be giving a talk where I do a whirlwind tour of the best features of Vue, while building an app from scratch.

Check it out here

💬 Bugs

"If debugging is the process of removing software bugs, then programming must be the process of putting them in." — Edsger Dijkstra

🧠 Spaced-repetition: Extract Conditional Pattern

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.

An extremely common question I get asked all the time is, "how do you know when to split up a component?"

I want to share a simple pattern with you that is basically fool-proof, and can be applied to lots of components with almost no thought.

When we encounter a v-if (or v-show) in our template, there's one main pattern we can use:

Extracting the body of each branch into its own component.

This is just one of many patterns included in Clean Components. There, we go into much more detail, examining each pattern more closely and really fine-tuning our understanding.

When we extract each branch body we go from this:

<div v-if="condition">    <div>      <!-- Lots of code here -->    </div>  </div>  <div v-else>    <div>      <!-- Lots of other code -->    </div>  </div>

To this:

<div v-if="condition">    <NewComponent />  </div>  <div v-else>    <OtherComponent />  </div>

We know we can do this for two reasons:

  • Each branch is semantically related
  • Each branch does distinct work

We know that each branch is semantically related, meaning all of that code works together to perform the same task.

Each branch also does distinct work — otherwise, why have a v-if at all?

This means it's a perfect opportunity to create new components.

And by replacing a large chunk of code with a well-named component that represents the code's intent, we make our code much more self-documenting. But we'll get to that later on.

🔗 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

评论

此博客中的热门博文

Weekly Vue News #141 - Share Styling Using Wrapper Components

Scripting News: Tuesday, October 8, 2024

Scripting News: Tuesday, August 20, 2024